2023-11-14 15:10:00 +00:00
|
|
|
import type { AuthModalSource } from '../components/Nav/AuthModal/types'
|
2023-11-28 13:18:25 +00:00
|
|
|
import type { Author, Result } from '../graphql/schema/core.gen'
|
2023-06-16 14:47:24 +00:00
|
|
|
import type { Accessor, JSX, Resource } from 'solid-js'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
2023-12-14 11:49:55 +00:00
|
|
|
import {
|
|
|
|
VerifyEmailInput,
|
|
|
|
LoginInput,
|
|
|
|
AuthToken,
|
|
|
|
User,
|
|
|
|
Authorizer,
|
|
|
|
ConfigType,
|
|
|
|
} from '@authorizerdev/authorizer-js'
|
|
|
|
import {
|
|
|
|
createContext,
|
|
|
|
createEffect,
|
|
|
|
createMemo,
|
|
|
|
createResource,
|
|
|
|
createSignal,
|
|
|
|
onMount,
|
|
|
|
useContext,
|
|
|
|
} from 'solid-js'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
2023-11-28 13:18:25 +00:00
|
|
|
import { apiClient } from '../graphql/client/core'
|
2023-06-14 17:19:30 +00:00
|
|
|
import { showModal } from '../stores/ui'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { useLocalize } from './localize'
|
|
|
|
import { useSnackbar } from './snackbar'
|
2022-11-13 19:35:57 +00:00
|
|
|
|
2023-12-14 11:49:55 +00:00
|
|
|
const config: ConfigType = {
|
|
|
|
authorizerURL: 'https://auth.discours.io',
|
|
|
|
redirectURL: 'https://discoursio-webapp.vercel.app/?modal=auth',
|
|
|
|
clientID: '9c113377-5eea-4c89-98e1-69302462fc08', // FIXME: use env?
|
|
|
|
}
|
|
|
|
|
2023-11-28 13:18:25 +00:00
|
|
|
export type SessionContextType = {
|
2023-12-14 11:49:55 +00:00
|
|
|
user: User | null
|
|
|
|
config: ConfigType
|
2023-11-28 13:18:25 +00:00
|
|
|
session: Resource<AuthToken>
|
2022-12-06 16:03:55 +00:00
|
|
|
isSessionLoaded: Accessor<boolean>
|
2023-11-28 13:18:25 +00:00
|
|
|
subscriptions: Accessor<Result>
|
|
|
|
author: Resource<Author | null>
|
2022-11-13 19:35:57 +00:00
|
|
|
isAuthenticated: Accessor<boolean>
|
2023-12-14 11:49:55 +00:00
|
|
|
isAuthWithCallback: Accessor<() => void>
|
2022-11-13 19:35:57 +00:00
|
|
|
actions: {
|
2023-12-03 10:22:42 +00:00
|
|
|
getToken: () => string
|
2023-11-28 13:18:25 +00:00
|
|
|
loadSession: () => AuthToken | Promise<AuthToken>
|
2023-10-19 15:05:22 +00:00
|
|
|
loadSubscriptions: () => Promise<void>
|
2023-06-14 17:19:30 +00:00
|
|
|
requireAuthentication: (
|
|
|
|
callback: (() => Promise<void>) | (() => void),
|
2023-11-14 15:10:00 +00:00
|
|
|
modalSource: AuthModalSource,
|
2023-06-14 17:19:30 +00:00
|
|
|
) => void
|
2023-11-28 13:18:25 +00:00
|
|
|
signIn: (params: LoginInput) => Promise<void>
|
2022-11-13 19:35:57 +00:00
|
|
|
signOut: () => Promise<void>
|
2023-11-28 13:18:25 +00:00
|
|
|
confirmEmail: (input: VerifyEmailInput) => Promise<void>
|
2023-12-14 11:49:55 +00:00
|
|
|
setIsSessionLoaded: (loaded: boolean) => void
|
|
|
|
setToken: (token: AuthToken | null) => void // setSession
|
|
|
|
setUser: (user: User | null) => void
|
|
|
|
authorizer: () => Authorizer
|
2022-11-13 19:35:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-14 10:02:08 +00:00
|
|
|
const SessionContext = createContext<SessionContextType>()
|
2022-11-13 19:35:57 +00:00
|
|
|
|
2022-11-14 10:02:08 +00:00
|
|
|
export function useSession() {
|
|
|
|
return useContext(SessionContext)
|
2022-11-13 19:35:57 +00:00
|
|
|
}
|
|
|
|
|
2023-11-02 17:43:22 +00:00
|
|
|
const EMPTY_SUBSCRIPTIONS = {
|
|
|
|
topics: [],
|
2023-11-14 15:10:00 +00:00
|
|
|
authors: [],
|
2023-11-02 17:43:22 +00:00
|
|
|
}
|
|
|
|
|
2023-12-14 11:49:55 +00:00
|
|
|
export const SessionProvider = (props: {
|
|
|
|
onStateChangeCallback(state: any): unknown
|
|
|
|
children: JSX.Element
|
|
|
|
}) => {
|
2022-12-06 16:03:55 +00:00
|
|
|
const [isSessionLoaded, setIsSessionLoaded] = createSignal(false)
|
2023-11-28 13:18:25 +00:00
|
|
|
const [subscriptions, setSubscriptions] = createSignal<Result>(EMPTY_SUBSCRIPTIONS)
|
2023-02-17 09:21:02 +00:00
|
|
|
const { t } = useLocalize()
|
2023-02-10 11:11:24 +00:00
|
|
|
const {
|
2023-11-14 15:10:00 +00:00
|
|
|
actions: { showSnackbar },
|
2023-02-10 11:11:24 +00:00
|
|
|
} = useSnackbar()
|
2023-12-14 11:49:55 +00:00
|
|
|
const [token, setToken] = createSignal<AuthToken>()
|
|
|
|
const [user, setUser] = createSignal<User>()
|
2023-11-28 13:18:25 +00:00
|
|
|
const loadSubscriptions = async (): Promise<void> => {
|
|
|
|
const result = await apiClient.getMySubscriptions()
|
|
|
|
if (result) {
|
|
|
|
setSubscriptions(result)
|
|
|
|
} else {
|
|
|
|
setSubscriptions(EMPTY_SUBSCRIPTIONS)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const getSession = async (): Promise<AuthToken> => {
|
2022-12-06 16:03:55 +00:00
|
|
|
try {
|
2023-12-14 12:13:35 +00:00
|
|
|
const t = getToken()
|
|
|
|
console.debug('[context.session]' + t)
|
2023-12-14 11:49:55 +00:00
|
|
|
const authResult = await authorizer().getSession({
|
2023-12-14 12:13:35 +00:00
|
|
|
Authorization: t,
|
2023-12-14 11:49:55 +00:00
|
|
|
})
|
|
|
|
if (authResult?.access_token) {
|
|
|
|
console.log(authResult)
|
|
|
|
setToken(authResult)
|
|
|
|
if (authResult.user) setUser(authResult.user)
|
|
|
|
loadSubscriptions()
|
|
|
|
return authResult
|
2022-12-06 16:03:55 +00:00
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error('getSession error:', error)
|
2023-12-14 00:04:07 +00:00
|
|
|
setToken(null)
|
|
|
|
setUser(null)
|
2022-12-06 16:03:55 +00:00
|
|
|
return null
|
2022-12-07 11:06:26 +00:00
|
|
|
} finally {
|
2023-11-01 11:17:31 +00:00
|
|
|
setTimeout(() => {
|
|
|
|
setIsSessionLoaded(true)
|
|
|
|
}, 0)
|
2022-12-06 16:03:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-28 13:18:25 +00:00
|
|
|
const [session, { refetch: loadSession, mutate }] = createResource<AuthToken>(getSession, {
|
2022-12-06 16:03:55 +00:00
|
|
|
ssrLoadFrom: 'initial',
|
2023-11-14 15:10:00 +00:00
|
|
|
initialValue: null,
|
2022-12-06 16:03:55 +00:00
|
|
|
})
|
2022-11-13 19:35:57 +00:00
|
|
|
|
2023-11-28 13:18:25 +00:00
|
|
|
const [author, { refetch: loadAuthor }] = createResource<Author | null>(
|
|
|
|
async () => {
|
2023-11-28 18:04:51 +00:00
|
|
|
const u = session()?.user
|
|
|
|
if (u) {
|
2023-12-13 23:56:44 +00:00
|
|
|
return (await apiClient.getAuthorId({ user: u.id })) ?? null
|
2023-11-28 13:18:25 +00:00
|
|
|
}
|
|
|
|
return null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ssrLoadFrom: 'initial',
|
|
|
|
initialValue: null,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
const isAuthenticated = createMemo(() => Boolean(session()?.user))
|
|
|
|
|
|
|
|
const signIn = async (params: LoginInput) => {
|
2023-12-03 16:41:59 +00:00
|
|
|
const authResult: AuthToken | void = await authorizer().login(params)
|
|
|
|
|
|
|
|
if (authResult && authResult.access_token) {
|
2023-12-14 00:04:07 +00:00
|
|
|
setToken(authResult)
|
2023-11-28 13:18:25 +00:00
|
|
|
mutate(authResult)
|
2023-12-03 16:41:59 +00:00
|
|
|
loadSubscriptions()
|
|
|
|
console.debug('signed in')
|
|
|
|
} else {
|
|
|
|
console.info((authResult as AuthToken).message)
|
2023-11-28 13:18:25 +00:00
|
|
|
}
|
2022-11-13 19:35:57 +00:00
|
|
|
}
|
|
|
|
|
2023-12-14 11:49:55 +00:00
|
|
|
const authorizer = createMemo(
|
|
|
|
() =>
|
|
|
|
new Authorizer({
|
|
|
|
authorizerURL: config.authorizerURL,
|
|
|
|
redirectURL: config.redirectURL,
|
|
|
|
clientID: config.clientID,
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
|
|
|
|
createEffect(() => {
|
|
|
|
if (props.onStateChangeCallback) {
|
|
|
|
props.onStateChangeCallback(token())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const [configuration, setConfig] = createSignal<ConfigType>(config)
|
|
|
|
|
|
|
|
onMount(async () => {
|
|
|
|
setIsSessionLoaded(false)
|
|
|
|
console.log('[context.session] loading...')
|
|
|
|
const metaRes = await authorizer().getMetaData()
|
|
|
|
setConfig({ ...config, ...metaRes, redirectURL: window.location.origin + '/?modal=auth' })
|
|
|
|
console.log('[context.session] refreshing session...')
|
|
|
|
const s = await getSession()
|
|
|
|
console.log(`[context.session] ${s}`)
|
|
|
|
setToken(s)
|
|
|
|
console.log('[context.session] loading author...')
|
|
|
|
await loadAuthor()
|
|
|
|
setIsSessionLoaded(true)
|
|
|
|
console.log('[context.session] loaded')
|
|
|
|
})
|
2023-06-14 17:19:30 +00:00
|
|
|
|
2023-12-14 11:49:55 +00:00
|
|
|
const [isAuthWithCallback, setIsAuthWithCallback] = createSignal<() => void>()
|
2023-11-02 17:43:22 +00:00
|
|
|
const requireAuthentication = async (callback: () => void, modalSource: AuthModalSource) => {
|
2023-06-14 17:19:30 +00:00
|
|
|
setIsAuthWithCallback(() => callback)
|
|
|
|
|
2023-12-14 00:04:07 +00:00
|
|
|
await authorizer().getProfile()
|
2023-11-02 17:43:22 +00:00
|
|
|
|
2023-06-14 17:19:30 +00:00
|
|
|
if (!isAuthenticated()) {
|
|
|
|
showModal('auth', modalSource)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-13 19:35:57 +00:00
|
|
|
const signOut = async () => {
|
2023-11-28 13:18:25 +00:00
|
|
|
await authorizer().logout()
|
2022-11-13 19:35:57 +00:00
|
|
|
mutate(null)
|
2023-12-14 00:04:07 +00:00
|
|
|
setToken(null)
|
|
|
|
setUser(null)
|
2023-11-02 17:43:22 +00:00
|
|
|
setSubscriptions(EMPTY_SUBSCRIPTIONS)
|
2023-02-10 11:11:24 +00:00
|
|
|
showSnackbar({ body: t("You've successfully logged out") })
|
2022-11-13 19:35:57 +00:00
|
|
|
}
|
|
|
|
|
2023-11-28 13:18:25 +00:00
|
|
|
const confirmEmail = async (input: VerifyEmailInput) => {
|
2023-11-28 18:04:51 +00:00
|
|
|
const at: void | AuthToken = await authorizer().verifyEmail(input)
|
|
|
|
if (at) {
|
2023-12-14 00:04:07 +00:00
|
|
|
setToken(at)
|
2023-11-28 18:04:51 +00:00
|
|
|
mutate(at)
|
2023-11-28 13:18:25 +00:00
|
|
|
}
|
2022-11-13 19:35:57 +00:00
|
|
|
}
|
|
|
|
|
2023-12-14 11:49:55 +00:00
|
|
|
const getToken = createMemo(() => token()?.access_token)
|
|
|
|
|
2022-11-13 19:35:57 +00:00
|
|
|
const actions = {
|
2023-12-14 11:49:55 +00:00
|
|
|
getToken,
|
2022-12-01 18:45:35 +00:00
|
|
|
loadSession,
|
2023-12-14 11:49:55 +00:00
|
|
|
loadSubscriptions,
|
2023-06-14 17:19:30 +00:00
|
|
|
requireAuthentication,
|
2022-11-13 19:35:57 +00:00
|
|
|
signIn,
|
|
|
|
signOut,
|
2023-10-19 15:05:22 +00:00
|
|
|
confirmEmail,
|
2023-12-14 11:49:55 +00:00
|
|
|
setIsSessionLoaded,
|
|
|
|
setToken,
|
|
|
|
setUser,
|
|
|
|
authorizer,
|
2022-11-13 19:35:57 +00:00
|
|
|
}
|
2023-10-19 15:05:22 +00:00
|
|
|
const value: SessionContextType = {
|
2023-12-14 11:49:55 +00:00
|
|
|
user: user(),
|
|
|
|
config: configuration(),
|
2023-10-19 15:05:22 +00:00
|
|
|
session,
|
|
|
|
subscriptions,
|
|
|
|
isSessionLoaded,
|
|
|
|
isAuthenticated,
|
2023-12-14 11:49:55 +00:00
|
|
|
author,
|
2023-11-14 15:10:00 +00:00
|
|
|
actions,
|
2023-12-14 11:49:55 +00:00
|
|
|
isAuthWithCallback,
|
2023-10-19 15:05:22 +00:00
|
|
|
}
|
2022-11-13 19:35:57 +00:00
|
|
|
|
2022-11-14 10:02:08 +00:00
|
|
|
return <SessionContext.Provider value={value}>{props.children}</SessionContext.Provider>
|
2022-11-13 19:35:57 +00:00
|
|
|
}
|