import jwtDecode from 'jwt-decode'; import { IUserData } from '../interfaces/IUserData'; import { GrantFlow } from '../shared/enums'; import { getAuthState, saveAuthState } from './state'; import { log } from './utils'; import { fetchWithCredentials } from './fetch'; export async function createSession(params: string, provider: string, localState: string, host: string, pkce?: string) { const state = await getAuthState(); const parsedParams = new URLSearchParams(params); if (!state.config) { throw new Error('No config found'); } const providerParams = state.config.providers?.[provider]; const providerOptions = state.config.config?.[provider]; if (!providerParams) { throw new Error('No provider params found (createSession)'); } const stateParam = parsedParams.get(providerParams.stateParam ?? 'state'); if (stateParam !== localState) { throw new Error('Invalid state'); } if (providerParams.grantType === GrantFlow.Token) { const expiresIn = parseInt(parsedParams.get(providerParams.expiresInName ?? 'expires_in') ?? '', 10) ?? 3600; const accessToken = parsedParams.get(providerParams.accessTokenName ?? 'access_token'); if (!accessToken) { throw new Error('No access token found'); } state.session = { provider, accessToken, userInfo: providerParams.userInfoTokenName ? parsedParams.get(providerParams.userInfoTokenName) ?? undefined : undefined, tokenType: parsedParams.get(providerParams.tokenTypeName ?? 'token_type') ?? 'Bearer', expiresAt: Date.now() + expiresIn * 1000, }; } if (providerParams.grantType === GrantFlow.AuthorizationCode || providerParams.grantType === GrantFlow.PKCE) { const accessCode = parsedParams.get(providerParams.authorizationCodeParam ?? 'code'); if (!accessCode) { throw new Error('No access code found'); } const redirectUrl = new URL(`${state.config?.basePath}/callback/${provider}`, host); const res = await globalThis.fetch(providerParams.tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ client_id: providerOptions.clientId, grant_type: 'authorization_code', code: accessCode, code_verifier: (await pkce) ?? '', redirect_uri: redirectUrl.href, }), }); if (res.status !== 200) { throw new Error('Could not get token'); } const response = await res.json(); const expiresIn = response[providerParams.expiresInName ?? ''] ?? 3600; const accessToken = response[providerParams.accessTokenName ?? 'access_token']; if (!accessToken) { throw new Error('No access token found'); } state.session = { provider, accessToken, tokenType: response[providerParams.tokenTypeName ?? 'token_type'] ?? 'Bearer', refreshToken: response[providerParams.refreshTokenName ?? 'refresh_token'], userInfo: response[providerParams.userInfoTokenName ?? ''], expiresAt: Date.now() + expiresIn * 1000, }; } await saveAuthState(state); return getUserData(); } export async function getUserData(): Promise { const state = await getAuthState(); if (!state.session) { log('state', state); throw new Error('No session found'); } const providerParams = state.config?.providers?.[state.session.provider]; if (state.session.userInfo) { const decoded: Record = jwtDecode(state.session.userInfo); return { provider: state.session.provider, data: (providerParams?.userInfoParser?.(decoded) ?? decoded) as Record, }; } else if (providerParams?.userInfoUrl) { const request = new Request(providerParams.userInfoUrl); const resp = await fetchWithCredentials(request); if (resp.status !== 200) { throw new Error('Could not get user info'); } const response = await resp.json(); return { data: (providerParams?.userInfoParser?.(response) ?? response) as Record, provider: state.session.provider, expiresAt: state.session.expiresAt, expiresAtDate: new Date(state.session.expiresAt), }; } throw new Error('No way to get user info'); } export async function deleteSession() { const state = await getAuthState(); state.session = undefined; await saveAuthState(state); }