Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Panoptes authentication in all packages #4803

Merged
merged 19 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6139f92
Get the user from the Panoptes JWT
eatyourgreens Jun 15, 2023
d6bb803
Update session auth in the classifier
eatyourgreens Jun 15, 2023
2461f2f
Update session auth in the content pages app
eatyourgreens Jun 16, 2023
b3c2d01
Classifier: add user loading state
eatyourgreens Jul 4, 2023
f6712f9
Move decodeJWT to the API client
eatyourgreens Jul 4, 2023
5faccd8
Maintain UPP when user refreshes in the background
eatyourgreens Jul 5, 2023
11702e3
Remove duplicate populateQueue on user change
eatyourgreens Jul 6, 2023
7ec5e02
move project preferences into ClassifierContainer
eatyourgreens Jul 5, 2023
2c3634e
Move project roles into ClassifierContainer
eatyourgreens Jul 7, 2023
fb9ef22
Move allowed workflows up to ClassifierContainer
eatyourgreens Jul 7, 2023
9ddea08
Refactor user loading state
eatyourgreens Jul 7, 2023
0f6b5cb
Test admin mode in the classifier container
eatyourgreens Jul 10, 2023
9b63a47
Update packages/lib-classifier/src/hooks/usePanoptesAuth.js
eatyourgreens Jul 11, 2023
602f736
Update packages/lib-classifier/src/components/Classifier/ClassifierCo…
eatyourgreens Jul 11, 2023
de49938
Update packages/app-project/stores/User/User.spec.js
eatyourgreens Jul 11, 2023
272187e
Update packages/lib-classifier/src/components/Classifier/Classifier.js
eatyourgreens Jul 11, 2023
baba30e
Clean up ClassifierContainer tests
eatyourgreens Jul 12, 2023
be05d50
Use an SWR-like API for all Panoptes user hooks
eatyourgreens Jul 12, 2023
0d5f5d0
update ClassifierContainer propTypes
eatyourgreens Jul 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/app-content-pages/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@sentry/nextjs": "~7.57.0",
"@zooniverse/async-states": "~0.0.1",
"@zooniverse/grommet-theme": "~3.1.0",
"@zooniverse/panoptes-js": "~0.3.0",
"@zooniverse/panoptes-js": "~0.4.0",
"@zooniverse/react-components": "~1.5.0",
"contentful": "~10.3.0",
"dotenv": "~16.3.0",
Expand Down
22 changes: 21 additions & 1 deletion packages/app-content-pages/src/shared/stores/User/User.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as panoptesJS from '@zooniverse/panoptes-js'
import asyncStates from '@zooniverse/async-states'
import { applySnapshot, flow, types } from 'mobx-state-tree'
import auth from 'panoptes-client/lib/auth'
Expand All @@ -6,6 +7,25 @@ import numberString from '../types/numberString'

import UserPersonalization from './UserPersonalization'

async function fetchPanoptesUser() {
try {
const token = await auth.checkBearerToken()
if (token) {
const { user, error } = await panoptesJS.auth.decodeJWT(token)
if (user) {
return user
}
if (error) {
throw error
}
}
return await auth.checkCurrent()
} catch (error) {
console.log(error)
return null
}
}

const User = types
.model('User', {
avatar_src: types.maybeNull(types.string),
Expand All @@ -31,7 +51,7 @@ const User = types
checkCurrent: flow(function * checkCurrent () {
self.loadingState = asyncStates.loading
try {
const userResource = yield auth.checkCurrent()
const userResource = yield fetchPanoptesUser()
self.loadingState = asyncStates.success
if (userResource) {
self.set(userResource)
Expand Down
2 changes: 1 addition & 1 deletion packages/app-project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@zooniverse/async-states": "~0.0.1",
"@zooniverse/classifier": "^0.0.1",
"@zooniverse/grommet-theme": "~3.1.0",
"@zooniverse/panoptes-js": "~0.3.0",
"@zooniverse/panoptes-js": "~0.4.0",
"@zooniverse/react-components": "~1.5.0",
"cookie": "~0.5.0",
"d3": "~6.7.0",
Expand Down
13 changes: 12 additions & 1 deletion packages/app-project/src/hooks/usePanoptesUser.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as panoptesJS from '@zooniverse/panoptes-js'
import auth from 'panoptes-client/lib/auth'
import useSWR from 'swr'

Expand All @@ -11,14 +12,24 @@ const SWRoptions = {

async function fetchPanoptesUser() {
try {
const token = await auth.checkBearerToken()
if (token) {
const { user, error } = await panoptesJS.auth.decodeJWT(token)
if (user) {
return user
}
if (error) {
throw error
}
}
return await auth.checkCurrent()
} catch (error) {
console.log(error)
return null
}
}

export default function usePanoptesUser(key) {
export default function usePanoptesUser(key = 'no-user') {
const { data } = useSWR(key, fetchPanoptesUser, SWRoptions)
return data
}
7 changes: 5 additions & 2 deletions packages/app-project/stores/User/User.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import asyncStates from '@zooniverse/async-states'
import { applySnapshot, flow, types } from 'mobx-state-tree'
import { applySnapshot, flow, getSnapshot, types } from 'mobx-state-tree'
import auth from 'panoptes-client/lib/auth'

import Collections from './Collections'
Expand Down Expand Up @@ -53,7 +53,10 @@ const User = types
},

set(user) {
const newUser = self.id !== user?.id
const { personalization } = getSnapshot(self)
const userSnapshot = {
personalization,
...user,
loadingState: asyncStates.success
}
Expand All @@ -64,7 +67,7 @@ const User = types
current_user_roles: 'owner,collaborator,contributor'
})
if (self.id) {
self.personalization.load()
self.personalization.load(newUser)
}
}
}))
Expand Down
112 changes: 102 additions & 10 deletions packages/app-project/stores/User/User.spec.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,80 @@
import asyncStates from '@zooniverse/async-states'
import * as client from '@zooniverse/panoptes-js'
import { expect } from 'chai'
import { when } from 'mobx'
import { getSnapshot } from 'mobx-state-tree'
import nock from 'nock'
import sinon from 'sinon'

import Store from '@stores/Store'
import initStore from '@stores/initStore'
eatyourgreens marked this conversation as resolved.
Show resolved Hide resolved
import placeholderEnv from '@stores/helpers/placeholderEnv'

const user = {
display_name: 'Jean-Luc Picard',
id: '1',
login: 'zootester1'
}

describe('stores > User', function () {
let rootStore = Store.create({}, placeholderEnv)
let userStore = rootStore.user
const user = {
display_name: 'Jean-Luc Picard',
id: '1',
login: 'zootester1'
}
let rootStore
let userStore

beforeEach(function () {
nock('https://panoptes-staging.zooniverse.org/api')
.persist()
.get('/users/1/recents')
.query(true)
.reply(200, { recents: [] })
.get('/users/2/recents')
.query(true)
.reply(200, { recents: [] })
.get('/collections')
.query(true)
.reply(200, { collections: [] })

nock('https://panoptes-staging.zooniverse.org/api')
.get('/project_preferences?project_id=1&user_id=1&http_cache=true')
.reply(200, {
project_preferences: [
{ activity_count: 23 }
]
})
.get('/project_preferences?project_id=1&user_id=1&http_cache=true')
.reply(200, {
project_preferences: [
{ activity_count: 25 }
]
})
.get('/project_preferences?project_id=1&user_id=2&http_cache=true')
.reply(200, {
project_preferences: [
{ activity_count: 27 }
]
})

nock('https://talk-staging.zooniverse.org')
.persist()
.get('/conversations')
.query(true)
.reply(200, { conversations: [] })
.get('/notifications')
.query(true)
.reply(200, { notifications: [] })

nock('https://graphql-stats.zooniverse.org')
.persist()
.post('/graphql')
.reply(200, { data: { statsCount: [] }})

rootStore = Store.create({ project: {
id: '1',
loadingState: asyncStates.success
}}, { client })
userStore = rootStore.user
})

afterEach(function () {
nock.cleanAll()
})

it('should exist', function () {
expect(userStore).to.be.ok()
Expand All @@ -33,15 +92,48 @@ describe('stores > User', function () {
expect(userStore.display_name).to.equal(user.display_name)
})

it('should clear the user', function () {
it('should clear the user', async function () {
userStore.set(user)
expect(userStore.id).to.equal(user.id)
expect(userStore.login).to.equal(user.login)
expect(userStore.display_name).to.equal(user.display_name)
await when(() => userStore.loadingState === asyncStates.success)

userStore.clear()

expect(userStore.id).to.be.null()
expect(userStore.login).to.be.null()
expect(userStore.display_name).to.be.null()
})

describe('with an existing user session', function () {

it('should refresh project preferences for the same user', async function () {
userStore.set(user)
const { personalization } = userStore

expect(userStore.id).to.equal(user.id)
await when(() => personalization.projectPreferences.loadingState === asyncStates.success)

userStore.set(user)
expect(userStore.personalization.projectPreferences.loadingState).to.equal(asyncStates.success)
expect(rootStore.appLoadingState).to.equal(asyncStates.success)
})

it('should load project preferences for new users', async function () {
userStore.set(user)
const { personalization } = userStore

expect(userStore.id).to.equal(user.id)
await when(() => personalization.projectPreferences.loadingState === asyncStates.success)

userStore.set({
display_name: 'Worf',
id: '2',
login: 'zootester2'
})
expect(userStore.personalization.projectPreferences.loadingState).to.equal(asyncStates.loading)
expect(rootStore.appLoadingState).to.equal(asyncStates.loading)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,14 @@ const UserPersonalization = types
}
},

load() {
load(newUser = true) {
self.notifications.fetchAndSubscribe()
self.projectPreferences.fetchResource()
self.stats.fetchDailyCounts()
if (newUser) {
self.projectPreferences.fetchResource()
} else {
self.projectPreferences.refreshSettings()
}
},

reset() {
Expand Down
4 changes: 2 additions & 2 deletions packages/lib-classifier/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
},
"peerDependencies": {
"@zooniverse/grommet-theme": "3.x.x",
"@zooniverse/panoptes-js": "~0.3.0",
"@zooniverse/panoptes-js": "~0.4.0",
"@zooniverse/react-components": "~1.x.x",
"grommet": "~2.x.x",
"grommet-icons": "~4.x.x",
Expand All @@ -79,7 +79,7 @@
"@visx/mock-data": "~3.0.0",
"@wojtekmaj/enzyme-adapter-react-17": "~0.8.0",
"@zooniverse/grommet-theme": "~3.1.0",
"@zooniverse/panoptes-js": "~0.3.0",
"@zooniverse/panoptes-js": "~0.4.0",
"@zooniverse/react-components": "~1.5.0",
"@zooniverse/standard": "~2.0.0",
"babel-loader": "~9.1.0",
Expand Down
Loading