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,
|
|
|
|
Authorizer,
|
|
|
|
ConfigType,
|
2023-12-24 08:16:41 +00:00
|
|
|
SignupInput,
|
2024-01-19 15:03:33 +00:00
|
|
|
AuthorizeResponse,
|
2024-01-19 18:28:52 +00:00
|
|
|
// GraphqlQueryInput,
|
2023-12-14 11:49:55 +00:00
|
|
|
} from '@authorizerdev/authorizer-js'
|
|
|
|
import {
|
|
|
|
createContext,
|
|
|
|
createEffect,
|
|
|
|
createMemo,
|
|
|
|
createResource,
|
|
|
|
createSignal,
|
2023-12-14 13:50:22 +00:00
|
|
|
on,
|
2023-12-24 21:29:25 +00:00
|
|
|
onCleanup,
|
2023-12-14 11:49:55 +00:00
|
|
|
onMount,
|
|
|
|
useContext,
|
|
|
|
} from 'solid-js'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
2023-12-19 09:34:24 +00:00
|
|
|
import { inboxClient } from '../graphql/client/chat'
|
2023-11-28 13:18:25 +00:00
|
|
|
import { apiClient } from '../graphql/client/core'
|
2023-12-19 09:34:24 +00:00
|
|
|
import { notifierClient } from '../graphql/client/notifier'
|
2023-12-25 05:47:11 +00:00
|
|
|
import { useRouter } from '../stores/router'
|
2023-06-14 17:19:30 +00:00
|
|
|
import { showModal } from '../stores/ui'
|
2023-12-25 07:32:53 +00:00
|
|
|
import { addAuthors } from '../stores/zine/authors'
|
2023-12-26 10:05:15 +00:00
|
|
|
|
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-24 08:16:41 +00:00
|
|
|
const defaultConfig: ConfigType = {
|
2023-12-14 11:49:55 +00:00
|
|
|
authorizerURL: 'https://auth.discours.io',
|
2024-01-19 15:03:33 +00:00
|
|
|
redirectURL: 'https://testing.discours.io',
|
|
|
|
clientID: 'b9038a34-ca59-41ae-a105-c7fbea603e24', // FIXME: use env?
|
2023-12-14 11:49:55 +00:00
|
|
|
}
|
|
|
|
|
2023-11-28 13:18:25 +00:00
|
|
|
export type SessionContextType = {
|
2023-12-14 11:49:55 +00:00
|
|
|
config: ConfigType
|
2023-11-28 13:18:25 +00:00
|
|
|
session: Resource<AuthToken>
|
2023-12-16 14:13:14 +00:00
|
|
|
author: Resource<Author | null>
|
2023-12-24 21:12:42 +00:00
|
|
|
authError: Accessor<string>
|
2022-12-06 16:03:55 +00:00
|
|
|
isSessionLoaded: Accessor<boolean>
|
2023-11-28 13:18:25 +00:00
|
|
|
subscriptions: Accessor<Result>
|
2023-12-14 11:49:55 +00:00
|
|
|
isAuthWithCallback: Accessor<() => void>
|
2023-12-24 08:16:41 +00:00
|
|
|
isAuthenticated: Accessor<boolean>
|
2022-11-13 19:35:57 +00:00
|
|
|
actions: {
|
2023-11-28 13:18:25 +00:00
|
|
|
loadSession: () => AuthToken | Promise<AuthToken>
|
2023-12-16 14:13:14 +00:00
|
|
|
setSession: (token: AuthToken | null) => void // setSession
|
|
|
|
loadAuthor: (info?: unknown) => Author | Promise<Author>
|
2023-12-20 16:54:20 +00:00
|
|
|
setAuthor: (a: Author) => void
|
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-12-24 08:16:41 +00:00
|
|
|
signUp: (params: SignupInput) => Promise<AuthToken | 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>
|
2024-01-19 18:24:37 +00:00
|
|
|
oauth: (provider: string) => Promise<void>
|
2023-12-24 08:16:41 +00:00
|
|
|
changePassword: (password: string, token: string) => void
|
|
|
|
confirmEmail: (input: VerifyEmailInput) => Promise<AuthToken | void> // email confirm callback is in auth.discours.io
|
2023-12-14 11:49:55 +00:00
|
|
|
setIsSessionLoaded: (loaded: boolean) => 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
|
|
|
|
}) => {
|
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-24 08:16:41 +00:00
|
|
|
const { searchParams, changeSearchParams } = useRouter()
|
2024-01-19 18:24:37 +00:00
|
|
|
const [configuration, setConfig] = createSignal<ConfigType>(defaultConfig)
|
|
|
|
const authorizer = createMemo(() => new Authorizer(configuration()))
|
2023-12-15 13:45:34 +00:00
|
|
|
|
2024-01-19 18:24:37 +00:00
|
|
|
createEffect(() => {
|
|
|
|
if (authorizer()) {
|
|
|
|
}
|
|
|
|
})
|
|
|
|
const [oauthState, setOauthState] = createSignal<string>()
|
2023-12-24 08:16:41 +00:00
|
|
|
// handle callback's redirect_uri
|
|
|
|
createEffect(async () => {
|
2024-01-19 18:24:37 +00:00
|
|
|
// oauth
|
|
|
|
const state = searchParams()?.state
|
|
|
|
if (state) {
|
|
|
|
setOauthState((_s) => state)
|
|
|
|
const scope = searchParams()?.scope
|
|
|
|
? searchParams()?.scope?.toString().split(' ')
|
|
|
|
: ['openid', 'profile', 'email']
|
|
|
|
if (scope) console.info(`[context.session] scope: ${scope}`)
|
|
|
|
const url = searchParams()?.redirect_uri || searchParams()?.redirectURL || window.location.href
|
|
|
|
setConfig((c: ConfigType) => ({ ...c, redirectURL: url })) // .split('?')[0]
|
|
|
|
changeSearchParams({ mode: 'confirm-email', modal: 'auth' }, true)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// handle email confirm
|
|
|
|
createEffect(() => {
|
2023-12-24 08:16:41 +00:00
|
|
|
const token = searchParams()?.token
|
|
|
|
const access_token = searchParams()?.access_token
|
2023-12-24 23:37:30 +00:00
|
|
|
if (access_token) changeSearchParams({ mode: 'confirm-email', modal: 'auth', access_token })
|
|
|
|
else if (token) changeSearchParams({ mode: 'change-password', modal: 'auth', token })
|
2023-12-16 14:13:14 +00:00
|
|
|
})
|
|
|
|
|
2023-12-24 08:16:41 +00:00
|
|
|
// load
|
2024-01-18 08:31:45 +00:00
|
|
|
let minuteLater
|
|
|
|
|
2023-12-24 08:16:41 +00:00
|
|
|
const [isSessionLoaded, setIsSessionLoaded] = createSignal(false)
|
2023-12-24 21:12:42 +00:00
|
|
|
const [authError, setAuthError] = createSignal('')
|
2023-12-24 08:16:41 +00:00
|
|
|
const [session, { refetch: loadSession, mutate: setSession }] = createResource<AuthToken>(
|
|
|
|
async () => {
|
|
|
|
try {
|
2023-12-24 21:29:25 +00:00
|
|
|
const s = await authorizer().getSession()
|
|
|
|
console.info('[context.session] loading session', s)
|
2024-01-18 08:31:45 +00:00
|
|
|
|
|
|
|
// Set session expiration time in local storage
|
|
|
|
const expires_at = new Date(Date.now() + s.expires_in * 1000)
|
|
|
|
localStorage.setItem('expires_at', `${expires_at.getTime()}`)
|
|
|
|
|
|
|
|
// Set up session expiration check timer
|
|
|
|
minuteLater = setTimeout(checkSessionIsExpired, 60 * 1000)
|
2023-12-24 21:29:25 +00:00
|
|
|
console.info(`[context.session] will refresh in ${s.expires_in / 60} mins`)
|
2024-01-18 08:31:45 +00:00
|
|
|
|
|
|
|
// Set the session loaded flag
|
|
|
|
setIsSessionLoaded(true)
|
|
|
|
|
2023-12-24 21:29:25 +00:00
|
|
|
return s
|
2023-12-25 04:05:04 +00:00
|
|
|
} catch (error) {
|
|
|
|
console.info('[context.session] cannot refresh session', error)
|
|
|
|
setAuthError(error)
|
2024-01-18 08:31:45 +00:00
|
|
|
|
|
|
|
// Set the session loaded flag even if there's an error
|
|
|
|
setIsSessionLoaded(true)
|
|
|
|
|
2023-12-24 08:16:41 +00:00
|
|
|
return null
|
2023-12-16 16:44:25 +00:00
|
|
|
}
|
2023-12-24 08:16:41 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
ssrLoadFrom: 'initial',
|
|
|
|
initialValue: null,
|
|
|
|
},
|
|
|
|
)
|
2023-12-16 14:13:14 +00:00
|
|
|
|
2024-01-18 08:31:45 +00:00
|
|
|
const checkSessionIsExpired = () => {
|
|
|
|
const expires_at_data = localStorage.getItem('expires_at')
|
|
|
|
|
|
|
|
if (expires_at_data) {
|
|
|
|
const expires_at = Number.parseFloat(expires_at_data)
|
|
|
|
const current_time = Date.now()
|
|
|
|
|
|
|
|
// Check if the session has expired
|
|
|
|
if (current_time >= expires_at) {
|
|
|
|
console.info('[context.session] Session has expired, refreshing.')
|
|
|
|
loadSession()
|
|
|
|
} else {
|
|
|
|
// Schedule the next check
|
|
|
|
minuteLater = setTimeout(checkSessionIsExpired, 60 * 1000)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onCleanup(() => clearTimeout(minuteLater))
|
2023-12-24 21:29:25 +00:00
|
|
|
|
2023-12-20 16:54:20 +00:00
|
|
|
const [author, { refetch: loadAuthor, mutate: setAuthor }] = createResource<Author | null>(
|
2023-11-28 13:18:25 +00:00
|
|
|
async () => {
|
2023-11-28 18:04:51 +00:00
|
|
|
const u = session()?.user
|
2024-01-10 12:39:02 +00:00
|
|
|
return u ? (await apiClient.getAuthorId({ user: u.id.trim() })) || null : null
|
2023-11-28 13:18:25 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
ssrLoadFrom: 'initial',
|
|
|
|
initialValue: null,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2023-12-24 08:16:41 +00:00
|
|
|
const [subscriptions, setSubscriptions] = createSignal<Result>(EMPTY_SUBSCRIPTIONS)
|
|
|
|
const loadSubscriptions = async (): Promise<void> => {
|
|
|
|
const result = await apiClient.getMySubscriptions()
|
|
|
|
setSubscriptions(result || EMPTY_SUBSCRIPTIONS)
|
|
|
|
}
|
2023-12-03 16:41:59 +00:00
|
|
|
|
2023-12-24 21:12:42 +00:00
|
|
|
// when session is loaded
|
2023-12-24 08:16:41 +00:00
|
|
|
createEffect(async () => {
|
|
|
|
if (session()) {
|
|
|
|
const token = session()?.access_token
|
|
|
|
if (token) {
|
2023-12-24 21:29:25 +00:00
|
|
|
// console.log('[context.session] token observer got token', token)
|
2023-12-24 08:16:41 +00:00
|
|
|
if (!inboxClient.private) {
|
|
|
|
apiClient.connect(token)
|
|
|
|
notifierClient.connect(token)
|
|
|
|
inboxClient.connect(token)
|
|
|
|
}
|
|
|
|
if (!author()) {
|
|
|
|
const a = await loadAuthor()
|
2023-12-25 07:32:53 +00:00
|
|
|
if (a) {
|
|
|
|
await loadSubscriptions()
|
|
|
|
addAuthors([a])
|
|
|
|
} else {
|
|
|
|
reset()
|
|
|
|
}
|
2023-12-24 08:16:41 +00:00
|
|
|
}
|
2023-12-24 21:12:42 +00:00
|
|
|
setIsSessionLoaded(true)
|
2023-12-24 08:16:41 +00:00
|
|
|
}
|
2023-12-24 12:56:30 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2023-12-24 21:44:01 +00:00
|
|
|
const reset = () => {
|
|
|
|
setIsSessionLoaded(true)
|
|
|
|
setSubscriptions(EMPTY_SUBSCRIPTIONS)
|
|
|
|
setSession(null)
|
|
|
|
setAuthor(null)
|
|
|
|
}
|
|
|
|
|
2023-12-24 08:16:41 +00:00
|
|
|
// initial effect
|
|
|
|
onMount(async () => {
|
|
|
|
const metaRes = await authorizer().getMetaData()
|
|
|
|
setConfig({ ...defaultConfig, ...metaRes, redirectURL: window.location.origin })
|
2024-01-18 15:57:10 +00:00
|
|
|
let s: AuthToken
|
2023-12-24 08:16:41 +00:00
|
|
|
try {
|
|
|
|
s = await loadSession()
|
2023-12-25 04:05:04 +00:00
|
|
|
} catch (error) {
|
|
|
|
console.warn('[context.session] load session failed', error)
|
2023-12-24 12:56:30 +00:00
|
|
|
}
|
2023-12-24 21:44:01 +00:00
|
|
|
if (!s) reset()
|
2023-12-24 08:16:41 +00:00
|
|
|
})
|
2023-12-14 11:49:55 +00:00
|
|
|
|
2023-12-24 08:16:41 +00:00
|
|
|
// callback state updater
|
2023-12-14 13:50:22 +00:00
|
|
|
createEffect(
|
|
|
|
on(
|
|
|
|
() => props.onStateChangeCallback,
|
|
|
|
() => {
|
2023-12-16 14:13:14 +00:00
|
|
|
props.onStateChangeCallback(session())
|
2023-12-14 13:50:22 +00:00
|
|
|
},
|
|
|
|
{ defer: true },
|
|
|
|
),
|
|
|
|
)
|
2023-12-14 11:49:55 +00:00
|
|
|
|
2023-12-24 08:16:41 +00:00
|
|
|
// require auth wrapper
|
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-24 08:16:41 +00:00
|
|
|
await loadSession()
|
2023-11-02 17:43:22 +00:00
|
|
|
|
2023-12-24 08:16:41 +00:00
|
|
|
if (!session()) {
|
2023-06-14 17:19:30 +00:00
|
|
|
showModal('auth', modalSource)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-24 08:16:41 +00:00
|
|
|
// authorizer api proxy methods
|
2024-01-19 18:24:37 +00:00
|
|
|
const signUp = async (params: SignupInput) => {
|
|
|
|
const authResult: void | AuthToken = await authorizer().signup(params)
|
2023-12-24 08:16:41 +00:00
|
|
|
if (authResult) setSession(authResult)
|
|
|
|
}
|
|
|
|
|
|
|
|
const signIn = async (params: LoginInput) => {
|
|
|
|
const authResult: AuthToken | void = await authorizer().login(params)
|
|
|
|
if (authResult) setSession(authResult)
|
|
|
|
}
|
|
|
|
|
2022-11-13 19:35:57 +00:00
|
|
|
const signOut = async () => {
|
2023-11-28 13:18:25 +00:00
|
|
|
await authorizer().logout()
|
2023-12-24 21:44:01 +00:00
|
|
|
reset()
|
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-12-24 08:16:41 +00:00
|
|
|
const changePassword = async (password: string, token: string) => {
|
|
|
|
const resp = await authorizer().resetPassword({ password, token, confirm_password: password })
|
|
|
|
console.debug('[context.session] change password response:', resp)
|
|
|
|
}
|
|
|
|
|
2023-11-28 13:18:25 +00:00
|
|
|
const confirmEmail = async (input: VerifyEmailInput) => {
|
2023-12-17 12:36:47 +00:00
|
|
|
console.debug(`[context.session] calling authorizer's verify email with`, input)
|
2023-12-24 21:12:42 +00:00
|
|
|
try {
|
|
|
|
const at: void | AuthToken = await authorizer().verifyEmail(input)
|
|
|
|
if (at) setSession(at)
|
|
|
|
return at
|
2023-12-25 04:05:04 +00:00
|
|
|
} catch (error) {
|
|
|
|
console.warn(error)
|
2023-12-24 21:12:42 +00:00
|
|
|
}
|
2022-11-13 19:35:57 +00:00
|
|
|
}
|
|
|
|
|
2024-01-19 18:24:37 +00:00
|
|
|
const oauth = async (oauthProvider: string) => {
|
2024-01-19 15:03:33 +00:00
|
|
|
console.debug(`[context.session] calling authorizer's oauth for`)
|
|
|
|
try {
|
2024-01-19 18:24:37 +00:00
|
|
|
// const data: GraphqlQueryInput = {}
|
|
|
|
// await authorizer().graphqlQuery(data)
|
2024-01-19 15:03:33 +00:00
|
|
|
const ar: AuthorizeResponse | void = await authorizer().oauthLogin(
|
|
|
|
oauthProvider,
|
|
|
|
[],
|
|
|
|
window.location.origin,
|
2024-01-19 18:24:37 +00:00
|
|
|
oauthState(),
|
2024-01-19 15:03:33 +00:00
|
|
|
)
|
|
|
|
console.debug(ar)
|
|
|
|
} catch (error) {
|
|
|
|
console.warn(error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-24 08:16:41 +00:00
|
|
|
const isAuthenticated = createMemo(() => Boolean(author()))
|
2022-11-13 19:35:57 +00:00
|
|
|
const actions = {
|
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,
|
2023-12-24 08:16:41 +00:00
|
|
|
signUp,
|
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,
|
2023-12-20 16:54:20 +00:00
|
|
|
setSession,
|
|
|
|
setAuthor,
|
2023-12-14 11:49:55 +00:00
|
|
|
authorizer,
|
2023-12-16 14:13:14 +00:00
|
|
|
loadAuthor,
|
2023-12-24 08:16:41 +00:00
|
|
|
changePassword,
|
2024-01-19 18:24:37 +00:00
|
|
|
oauth,
|
2022-11-13 19:35:57 +00:00
|
|
|
}
|
2023-10-19 15:05:22 +00:00
|
|
|
const value: SessionContextType = {
|
2023-12-24 21:12:42 +00:00
|
|
|
authError,
|
2023-12-14 11:49:55 +00:00
|
|
|
config: configuration(),
|
2023-10-19 15:05:22 +00:00
|
|
|
session,
|
|
|
|
subscriptions,
|
|
|
|
isSessionLoaded,
|
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-12-24 08:16:41 +00:00
|
|
|
isAuthenticated,
|
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
|
|
|
}
|