diff --git a/packages/core/src/@types/config.ts b/packages/core/src/@types/config.ts index a658560a..e35a56d9 100644 --- a/packages/core/src/@types/config.ts +++ b/packages/core/src/@types/config.ts @@ -124,6 +124,20 @@ export type AuthConfig< logger?: boolean | Logger /** * Defines the session management strategy for Aura Auth. It determines how sessions are created, stored, and validated. + * @example + * // Stateless JWT strategy (default) + * session: { + * strategy: "jwt", + * jwt: { + * issuer: "https://auth.example.com", + * } + * } + * + * // Database-backed session strategy + * session: { + * strategy: "database", + * adapter: prismaAdapter({ client: prismaClient }), + * } */ session?: SessionConfig /** diff --git a/packages/core/src/@types/internal.ts b/packages/core/src/@types/internal.ts index fe13bad8..8157c362 100644 --- a/packages/core/src/@types/internal.ts +++ b/packages/core/src/@types/internal.ts @@ -42,39 +42,6 @@ import type { TypedJWTPayload } from "@aura-stack/jose" import type { SessionWithUserEntity } from "@/@types/entities.ts" import type { createCookieManager } from "@/session/cookie-manager.ts" -// #region API -/** - * Utility to merge the internal router global context (`ctx`) with per-function options. - * Used by implementation-level API functions in `src/api/*`. - */ -export type FunctionAPIContext = Prettify< - { - ctx: RouterGlobalContext - } & Options -> - -// #region Config -export type InternalCookieStoreConfig = Record - -/** - * Normalized internal logger with resolved level and structured log function. - */ -export interface InternalLogger { - level: LogLevel - log: typeof createLogEntry -} - -/** - * Identity validation settings used when building session strategy and OAuth profile mapping. - * Controls the Zod schema and how unknown keys are handled on user objects. - */ -export interface InternalIdentityConfig { - schema?: Schema - schemaAsPartial?: Schema - skipValidation?: boolean - unknownKeys?: "passthrough" | "strict" | "strip" -} - /** * Runtime context passed into auth actions and API handlers: OAuth map, cookies, JWT, session strategy, trusted origins, etc. * This is the fully resolved configuration surface after `createAuth` initializes defaults. @@ -82,20 +49,18 @@ export interface InternalIdentityConfig> { oauth: OAuthProviderRecord credentials?: CredentialsConfig - cookies: InternalCookieStoreConfig - jose: JoseInstance + cookies: InternalCookieConfig + jose: InternalJoseConfig secret?: JWTKey baseURL?: string basePath: RoutePattern trustedProxyHeaders: boolean | TrustedProxyHeadersSource[] trustedOrigins?: TrustedOrigin[] | ((request: Request) => Promise | TrustedOrigin[]) logger?: InternalLogger - sessionStrategy: SessionStrategy + session: InternalSessionConfig identity: SchemaRegistryContext signUp?: SignUpConfig - jwtManager: JWTManager rateLimiters: InferRules> - sessionConfig: SessionConfig emailVerification?: EmailVerificationConfig passwordReset?: PasswordResetConfig emailChange?: EmailChangeConfig @@ -108,37 +73,22 @@ export interface SchemaRegistryContext { unknownKeys: "passthrough" | "strict" | "strip" } +export type InternalSessionConfig = { + config: Config + strategy: SessionStrategy +} + export type InternalContext = RouterGlobalContext< FromShapeToObject, SignUpSchema -> & { - cookieConfig: { - trusted: InternalCookieStoreConfig - untrusted: InternalCookieStoreConfig - } -} - -export type InternalContextForStateful = Omit, "sessionConfig"> & { - sessionConfig: StatefulStrategyConfig -} - -export interface CreateSessionStrategyOptions { - ctx: InternalContext - cookies: () => InternalCookieStoreConfig -} +> -export interface InternalSessionContext { - ctx: Ctx - cookies: () => InternalCookieStoreConfig - cookieManager: CookieManager +export type InternalContextForStateful = RouterGlobalContext & { + session: InternalSessionConfig } -export type InternalStatefulContext = InternalSessionContext - -export type InternalStatelessContext = InternalSessionContext - -export type InternalContextForStateless = Omit, "sessionConfig"> & { - sessionConfig: StatelessStrategyConfig +export type InternalContextForStateless = RouterGlobalContext & { + session: InternalSessionConfig } export type InternalExpirationResult = @@ -173,11 +123,58 @@ export interface OnCreateUserContext { } /** Minimal token issue/verify surface used by session code paths. */ -export type JWTManager = { +export type JoseManager = { createToken(user: TypedJWTPayload>): Promise verifyToken(token: string): Promise> } +export interface InternalCookieConfig { + store: InternalCookieStoreConfig + precomputed: { + trusted: InternalCookieStoreConfig + untrusted: InternalCookieStoreConfig + } + manager: CookieManager +} + +export interface InternalJoseConfig { + instance: JoseInstance + manager: JoseManager +} + +// #region API +/** + * Utility to merge the internal router global context (`ctx`) with per-function options. + * Used by implementation-level API functions in `src/api/*`. + */ +export type FunctionAPIContext = Prettify< + { + ctx: RouterGlobalContext + } & Options +> + +// #region Config +export type InternalCookieStoreConfig = Record + +/** + * Normalized internal logger with resolved level and structured log function. + */ +export interface InternalLogger { + level: LogLevel + log: typeof createLogEntry +} + +/** + * Identity validation settings used when building session strategy and OAuth profile mapping. + * Controls the Zod schema and how unknown keys are handled on user objects. + */ +export interface InternalIdentityConfig { + schema?: Schema + schemaAsPartial?: Schema + skipValidation?: boolean + unknownKeys?: "passthrough" | "strict" | "strip" +} + // #region Session export interface AsymmetricKeyPairFromEnv { publicKey: string diff --git a/packages/core/src/actions/callback/callback.ts b/packages/core/src/actions/callback/callback.ts index f1f57388..0bdd83ce 100644 --- a/packages/core/src/actions/callback/callback.ts +++ b/packages/core/src/actions/callback/callback.ts @@ -22,7 +22,7 @@ export const callbackAction = (oauth: OAuthProviderRecord) => { searchParams: { code, state }, context, } = ctx - return await context.sessionStrategy.oauthCallback({ + return await context.session.strategy.oauthCallback({ oauthId, request, params: { code, state }, diff --git a/packages/core/src/actions/csrfToken/csrf-token.ts b/packages/core/src/actions/csrfToken/csrf-token.ts index cf896464..83adc684 100644 --- a/packages/core/src/actions/csrfToken/csrf-token.ts +++ b/packages/core/src/actions/csrfToken/csrf-token.ts @@ -18,13 +18,13 @@ export const csrfTokenAction = createEndpoint( request, context: { jose, cookies, logger }, } = ctx - const token = getOptionalCookie(request, cookies.csrfToken.name) + const token = getOptionalCookie(request, cookies.store.csrfToken.name) logger?.log("CSRF_TOKEN_REQUESTED", { structuredData: { has_token: Boolean(token) } }) - const csrfToken = await createCSRF(jose, token) + const csrfToken = await createCSRF(jose.instance, token) logger?.log("CSRF_TOKEN_ISSUED", { structuredData: { issued: Boolean(csrfToken) } }) const headers = new Headers(secureApiHeaders) - headers.append("Set-Cookie", setCookie(cookies.csrfToken.name, csrfToken, cookies.csrfToken.attributes)) + headers.append("Set-Cookie", setCookie(cookies.store.csrfToken.name, csrfToken, cookies.store.csrfToken.attributes)) return ctx.json({ csrfToken }, { headers }) }, config diff --git a/packages/core/src/api/cancel-email-change.ts b/packages/core/src/api/cancel-email-change.ts index 84ce25de..30687bc2 100644 --- a/packages/core/src/api/cancel-email-change.ts +++ b/packages/core/src/api/cancel-email-change.ts @@ -14,7 +14,7 @@ export const cancelEmailChange = async ({ skipCSRFCheck = false, doubleSubmitToken = undefined, }: FunctionAPIContext): Promise => { - const { logger, sessionStrategy } = ctx + const { logger, session } = ctx try { logger?.log("CANCEL_EMAIL_CHANGE_INITIATED", { structuredData: { @@ -36,7 +36,7 @@ export const cancelEmailChange = async ({ return rateLimit } - await sessionStrategy.cancelEmailChange({ + await session.strategy.cancelEmailChange({ token: payload?.token, userId: undefined, }) @@ -51,7 +51,7 @@ export const cancelEmailChange = async ({ return rateLimit } - await sessionStrategy.cancelEmailChange({ + await session.strategy.cancelEmailChange({ token: undefined, userId: userId!, }) diff --git a/packages/core/src/api/change-email.ts b/packages/core/src/api/change-email.ts index bd2af621..1816b7c5 100644 --- a/packages/core/src/api/change-email.ts +++ b/packages/core/src/api/change-email.ts @@ -15,7 +15,7 @@ export const changeEmail = async ({ doubleSubmitToken = undefined, skipCSRFCheck = false, }: FunctionAPIContext): Promise => { - const { logger, sessionStrategy, emailChange } = ctx + const { logger, session, emailChange } = ctx if (!emailChange?.sendEmailChangeVerification) { throw new AuraAuthError({ code: "CHANGE_EMAIL_NOT_IMPLEMENTED" }) } @@ -39,7 +39,7 @@ export const changeEmail = async ({ if (rateLimit) { return rateLimit } - const { email, confirmToken, confirmURL, cancelToken, cancelURL } = await sessionStrategy.changeEmail({ + const { email, confirmToken, confirmURL, cancelToken, cancelURL } = await session.strategy.changeEmail({ origin, userId: userId!, currentPassword: payload.currentPassword, diff --git a/packages/core/src/api/change-password.ts b/packages/core/src/api/change-password.ts index fa9e7c3f..adf45a48 100644 --- a/packages/core/src/api/change-password.ts +++ b/packages/core/src/api/change-password.ts @@ -14,7 +14,7 @@ export const changePassword = async ({ skipCSRFCheck = false, doubleSubmitToken = undefined, }: FunctionAPIContext): Promise => { - const { logger, sessionStrategy } = ctx + const { logger, session } = ctx try { logger?.log("CHANGE_PASSWORD_INITIATED", { structuredData: { @@ -37,7 +37,7 @@ export const changePassword = async ({ return rateLimit } - await sessionStrategy.changePassword({ + await session.strategy.changePassword({ userId: userId!, currentPassword: payload.currentPassword, newPassword: payload.newPassword, diff --git a/packages/core/src/api/forgot-password.ts b/packages/core/src/api/forgot-password.ts index e208d013..3d81896d 100644 --- a/packages/core/src/api/forgot-password.ts +++ b/packages/core/src/api/forgot-password.ts @@ -14,7 +14,7 @@ export const forgotPassword = async ( redirect: redirectInit = true, }: FunctionAPIContext ): Promise => { - const { logger, sessionStrategy, passwordReset } = ctx + const { logger, session, passwordReset } = ctx try { logger?.log("FORGOT_PASSWORD_INITIATED") const { request, rateLimit, origin } = await createValidation( @@ -27,7 +27,7 @@ export const forgotPassword = async ( if (rateLimit) { return rateLimit as ForgotPasswordAPIReturn } - const { token, url } = await sessionStrategy.forgotPassword({ email, origin }) + const { token, url } = await session.strategy.forgotPassword({ email, origin }) await passwordReset?.sendPasswordResetEmail?.({ email, token, url }) const builder = new HeadersBuilder(secureApiHeaders) diff --git a/packages/core/src/api/get-provider-tokens.ts b/packages/core/src/api/get-provider-tokens.ts index 905475ec..d61bfa48 100644 --- a/packages/core/src/api/get-provider-tokens.ts +++ b/packages/core/src/api/get-provider-tokens.ts @@ -25,7 +25,7 @@ export const getProviderTokens = async ( if (rateLimit) { return rateLimit as unknown as GetProviderTokensAPIReturn } - const getTokens = await ctx.sessionStrategy.getProviderTokens({ + const getTokens = await ctx.session.strategy.getProviderTokens({ oauthId, request, provider, diff --git a/packages/core/src/api/get-session.ts b/packages/core/src/api/get-session.ts index 5142c534..7ef85943 100644 --- a/packages/core/src/api/get-session.ts +++ b/packages/core/src/api/get-session.ts @@ -11,7 +11,7 @@ export const getSession = async ({ headers: headersInit, }: FunctionAPIContext): Promise> => { const headers = new HeadersBuilder(secureApiHeaders) - .setCookie(ctx.cookies.sessionToken.name, "", getExpiredCookie(ctx.cookies.sessionToken.attributes)) + .setCookie(ctx.cookies.store.sessionToken.name, "", getExpiredCookie(ctx.cookies.store.sessionToken.attributes)) .toHeaders() const unauthorizedError = { code: "GET_SESSION_FAILED", @@ -25,7 +25,7 @@ export const getSession = async ({ toResponse: () => Response.json({ success: false, session: null }, { status: 401, headers }), } try { - const { session, headers } = await ctx.sessionStrategy.getSession({ + const { session, headers } = await ctx.session.strategy.getSession({ headers: toStandardizedHeaders(headersInit), }) if (!session) return unauthorized diff --git a/packages/core/src/api/is-provider-connected.ts b/packages/core/src/api/is-provider-connected.ts index 49155d79..6c2f45ce 100644 --- a/packages/core/src/api/is-provider-connected.ts +++ b/packages/core/src/api/is-provider-connected.ts @@ -28,7 +28,7 @@ export const isProviderConnected = async ( return rateLimit } - const connected = await ctx.sessionStrategy.isProviderConnected({ + const connected = await ctx.session.strategy.isProviderConnected({ oauthId, headers, sessionByToken: session!, diff --git a/packages/core/src/api/refresh-userInfo.ts b/packages/core/src/api/refresh-userInfo.ts index c73c0af1..5785434a 100644 --- a/packages/core/src/api/refresh-userInfo.ts +++ b/packages/core/src/api/refresh-userInfo.ts @@ -82,7 +82,7 @@ export const refreshUserInfo = async ( structuredData: { provider: oauth, userId: userInfo.sub }, }) - const { session, headers: newHeaders } = await ctx.sessionStrategy.refreshUserInfo({ + const { session, headers: newHeaders } = await ctx.session.strategy.refreshUserInfo({ headers, user: userInfo, }) diff --git a/packages/core/src/api/reset-password.ts b/packages/core/src/api/reset-password.ts index 7f1e4f28..f9634aac 100644 --- a/packages/core/src/api/reset-password.ts +++ b/packages/core/src/api/reset-password.ts @@ -12,7 +12,7 @@ export const resetPassword = async ({ redirectTo, redirect: redirectInit = true, }: FunctionAPIContext): Promise => { - const { logger, sessionStrategy } = ctx + const { logger, session } = ctx try { logger?.log("RESET_PASSWORD_INITIATED") const { request, rateLimit } = await createValidation( @@ -26,7 +26,7 @@ export const resetPassword = async ({ return rateLimit as ResetPasswordAPIReturn } - await sessionStrategy.resetPassword(payload) + await session.strategy.resetPassword(payload) const builder = new HeadersBuilder(secureApiHeaders) const { redirect, redirectURL } = await resolveApiRedirect(ctx, request, redirectInit, redirectTo, builder) diff --git a/packages/core/src/api/revoke-token.ts b/packages/core/src/api/revoke-token.ts index 1d2bb80d..c5f1c831 100644 --- a/packages/core/src/api/revoke-token.ts +++ b/packages/core/src/api/revoke-token.ts @@ -42,7 +42,7 @@ export const revokeToken = async ( return rateLimit as RevokeTokenAPIReturn } - const revokeHeaders = await ctx.sessionStrategy.revokeToken({ + const revokeHeaders = await ctx.session.strategy.revokeToken({ oauthId, provider, headers, diff --git a/packages/core/src/api/send-magic-link.ts b/packages/core/src/api/send-magic-link.ts index e331a5a9..0b173303 100644 --- a/packages/core/src/api/send-magic-link.ts +++ b/packages/core/src/api/send-magic-link.ts @@ -17,7 +17,7 @@ export const sendMagicLink = async ( doubleSubmitToken = undefined, }: FunctionAPIContext ): Promise => { - const { logger, sessionStrategy, magicLinks } = ctx + const { logger, session, magicLinks } = ctx if (!magicLinks?.sendMagicLinkEmail) { throw new AuraAuthError({ code: "MAGIC_LINK_SEND_EMAIL_NOT_IMPLEMENTED" }) } @@ -40,7 +40,7 @@ export const sendMagicLink = async ( if (rateLimit) { return rateLimit as SendMagicLinkAPIReturn } - const { token, url } = await sessionStrategy.sendMagicLink({ + const { token, url } = await session.strategy.sendMagicLink({ email, origin, allowSignUp: magicLinks?.allowSignUp ?? false, diff --git a/packages/core/src/api/send-verification-email.ts b/packages/core/src/api/send-verification-email.ts index ae610889..c2b5b3bb 100644 --- a/packages/core/src/api/send-verification-email.ts +++ b/packages/core/src/api/send-verification-email.ts @@ -17,7 +17,7 @@ export const sendVerificationEmail = async ( doubleSubmitToken = undefined, }: FunctionAPIContext ): Promise => { - const { logger, sessionStrategy, emailVerification } = ctx + const { logger, session, emailVerification } = ctx if (!emailVerification?.sendVerificationEmail) { throw new AuraAuthError({ code: "SEND_VERIFICATION_EMAIL_NOT_IMPLEMENTED" }) } @@ -35,7 +35,7 @@ export const sendVerificationEmail = async ( if (rateLimit) { return rateLimit as SendVerificationEmailAPIReturn } - const { token, url } = await sessionStrategy.sendVerificationEmail({ + const { token, url } = await session.strategy.sendVerificationEmail({ email, origin, }) diff --git a/packages/core/src/api/sign-in-credentials.ts b/packages/core/src/api/sign-in-credentials.ts index d69fdef9..a2ff4873 100644 --- a/packages/core/src/api/sign-in-credentials.ts +++ b/packages/core/src/api/sign-in-credentials.ts @@ -16,7 +16,7 @@ export const signInCredentials = async ({ skipCSRFCheck = false, doubleSubmitToken = undefined, }: FunctionAPIContext): Promise => { - const { cookies, credentials, sessionStrategy, logger } = ctx + const { cookies, credentials, logger, session: sessionConfig } = ctx try { ctx.logger?.log("SIGN_IN_CREDENTIALS_INITIATED", { structuredData: { @@ -46,16 +46,16 @@ export const signInCredentials = async ({ if (!session) { throw new AuraAuthError({ code: "AUTH_CREDENTIALS_INVALID" }) } - const sessionToken = await sessionStrategy.signInCredentials({ + const sessionToken = await sessionConfig.strategy.signInCredentials({ request, payload: session, }) - const csrfToken = await createCSRF(ctx.jose) + const csrfToken = await createCSRF(ctx.jose.instance) logger?.log("CREDENTIALS_SIGN_IN_SUCCESS") const builder = new HeadersBuilder(secureApiHeaders) - .setCookie(cookies.csrfToken.name, csrfToken, cookies.csrfToken.attributes) - .setCookie(cookies.sessionToken.name, sessionToken, cookies.sessionToken.attributes) + .setCookie(cookies.store.csrfToken.name, csrfToken, cookies.store.csrfToken.attributes) + .setCookie(cookies.store.sessionToken.name, sessionToken, cookies.store.sessionToken.attributes) const { redirect: shouldRedirectServer, redirectURL } = await resolveApiRedirect( ctx, diff --git a/packages/core/src/api/sign-in.ts b/packages/core/src/api/sign-in.ts index 3484b785..b1deaeb8 100644 --- a/packages/core/src/api/sign-in.ts +++ b/packages/core/src/api/sign-in.ts @@ -43,7 +43,7 @@ export const signIn = async ( } } - const { success, signInURL, headers } = await ctx.sessionStrategy.signIn({ + const { success, signInURL, headers } = await ctx.session.strategy.signIn({ oauthId, provider, request, diff --git a/packages/core/src/api/sign-out.ts b/packages/core/src/api/sign-out.ts index cf3f38a6..9d71b6b9 100644 --- a/packages/core/src/api/sign-out.ts +++ b/packages/core/src/api/sign-out.ts @@ -33,7 +33,7 @@ export const signOut = async ({ return rateLimit } - responseHeaders = await ctx.sessionStrategy.destroySession({ + responseHeaders = await ctx.session.strategy.destroySession({ sessionByToken: session!, }) diff --git a/packages/core/src/api/sign-up.ts b/packages/core/src/api/sign-up.ts index dbbcc04b..fe85f804 100644 --- a/packages/core/src/api/sign-up.ts +++ b/packages/core/src/api/sign-up.ts @@ -16,7 +16,7 @@ export const signUp = async = Record>): Promise => { - const { signUp, cookies, sessionStrategy, logger } = ctx + const { signUp, cookies, session, logger } = ctx try { ctx.logger?.log("SIGN_UP_INITIATED", { structuredData: { @@ -45,16 +45,16 @@ export const signUp = async = Record({ return rateLimit as UpdateSessionAPIReturn } - const { session, headers } = await ctx.sessionStrategy.refreshSession({ + const { session, headers } = await ctx.session.strategy.refreshSession({ session: sessionInit, headers: validatedHeaders, }) diff --git a/packages/core/src/api/verify-email-change.ts b/packages/core/src/api/verify-email-change.ts index 39043870..e04ccac1 100644 --- a/packages/core/src/api/verify-email-change.ts +++ b/packages/core/src/api/verify-email-change.ts @@ -14,7 +14,7 @@ export const verifyEmailChange = async ({ skipCSRFCheck = false, doubleSubmitToken = undefined, }: FunctionAPIContext): Promise => { - const { logger, sessionStrategy } = ctx + const { logger, session } = ctx try { logger?.log("VERIFY_EMAIL_CHANGE_INITIATED", { structuredData: { @@ -35,7 +35,7 @@ export const verifyEmailChange = async ({ if (rateLimit) { return rateLimit } - await sessionStrategy.verifyEmailChange({ + await session.strategy.verifyEmailChange({ sessionId: sessionId!, userId: userId!, token: payload.token, diff --git a/packages/core/src/api/verify-email.ts b/packages/core/src/api/verify-email.ts index f2996251..2def82bb 100644 --- a/packages/core/src/api/verify-email.ts +++ b/packages/core/src/api/verify-email.ts @@ -14,7 +14,7 @@ export const verifyEmail = async ( redirect: redirectInit = true, }: FunctionAPIContext ): Promise => { - const { logger, sessionStrategy } = ctx + const { logger, session } = ctx try { logger?.log("VERIFY_EMAIL_INITIATED") const { request, rateLimit } = await createValidation( @@ -28,7 +28,7 @@ export const verifyEmail = async ( return rateLimit as VerifyEmailAPIReturn } - await sessionStrategy.verifyEmail({ token }) + await session.strategy.verifyEmail({ token }) const builder = new HeadersBuilder(secureApiHeaders) const { redirect, redirectURL } = await resolveApiRedirect(ctx, request, redirectInit, redirectTo, builder) diff --git a/packages/core/src/api/verify-magic-link.ts b/packages/core/src/api/verify-magic-link.ts index 9927e012..72093859 100644 --- a/packages/core/src/api/verify-magic-link.ts +++ b/packages/core/src/api/verify-magic-link.ts @@ -15,7 +15,7 @@ export const verifyMagicLink = async ( redirect: redirectInit = true, }: FunctionAPIContext ): Promise => { - const { logger, sessionStrategy, cookies } = ctx + const { logger, session, cookies } = ctx try { logger?.log("VERIFY_MAGIC_LINK_INITIATED") const { request, rateLimit } = await createValidation( @@ -29,13 +29,13 @@ export const verifyMagicLink = async ( return rateLimit } - const { sessionToken } = await sessionStrategy.verifyMagicLink({ token, request }) + const { sessionToken } = await session.strategy.verifyMagicLink({ token, request }) logger?.log("VERIFY_MAGIC_LINK_SUCCESS") - const csrfToken = await createCSRF(ctx.jose) + const csrfToken = await createCSRF(ctx.jose.instance) const builder = new HeadersBuilder(secureApiHeaders) - .setCookie(cookies.csrfToken.name, csrfToken, cookies.csrfToken.attributes) - .setCookie(cookies.sessionToken.name, sessionToken, cookies.sessionToken.attributes) + .setCookie(cookies.store.csrfToken.name, csrfToken, cookies.store.csrfToken.attributes) + .setCookie(cookies.store.sessionToken.name, sessionToken, cookies.store.sessionToken.attributes) const { redirect, redirectURL } = await resolveApiRedirect(ctx, request, redirectInit, redirectTo, builder) return { diff --git a/packages/core/src/config/context.ts b/packages/core/src/config/context.ts index 129305b4..ec99611e 100644 --- a/packages/core/src/config/context.ts +++ b/packages/core/src/config/context.ts @@ -1,4 +1,4 @@ -import { cookies } from "@/config/cookies.ts" +import { getPrecomputedCookies } from "@/config/cookies.ts" import { createJoseInstance } from "@/jose.ts" import { getSaltConfig } from "@/config/get-salt.ts" import { isStatelessStrategy } from "@/shared/assert.ts" @@ -10,6 +10,7 @@ import { getBasePathConfig } from "@/config/get-base-path.ts" import { createSessionStrategy } from "@/session/strategy.ts" import { createJoseManager } from "@/session/jose-manager.ts" import { getOAuthProvidersConfig } from "@/config/get-oauth.ts" +import { createCookieManager } from "@/session/cookie-manager.ts" import { getEmailChangeConfig } from "@/config/get-email-change.ts" import { getCredentialsConfig } from "@/config/get-credentials.ts" import { createProxyLogger } from "@/config/logger/create-logger.ts" @@ -30,12 +31,19 @@ export const createContext = >(secret, salt, config?.session) - const { trusted, untrusted } = cookies(config, logger) + const precomputed = getPrecomputedCookies(config, logger) const ctx = { credentials: getCredentialsConfig(config), - cookies: untrusted, - jose, + cookies: { + store: precomputed.untrusted, + precomputed, + manager: createCookieManager(() => ctx.cookies.store), + }, + jose: { + instance: jose, + manager: createJoseManager(isStatelessStrategy(config?.session) ? config?.session?.jwt : undefined, jose), + }, secret, baseURL: getBaseURLConfig(config), basePath: getBasePathConfig(config), @@ -43,20 +51,17 @@ export const createContext = - ctx.sessionStrategy = createSessionStrategy({ - ctx, - cookies: () => ctx.cookies, - }) + ctx.session.strategy = createSessionStrategy(ctx) return ctx } diff --git a/packages/core/src/config/cookies.ts b/packages/core/src/config/cookies.ts index ed22663b..3f4d8aac 100644 --- a/packages/core/src/config/cookies.ts +++ b/packages/core/src/config/cookies.ts @@ -3,7 +3,7 @@ import type { AuthConfig } from "@/@types/config.ts" import type { InternalLogger } from "@/@types/internal.ts" import type { Identities, SchemaTypes } from "@/identity/index.ts" -export const cookies = ( +export const getPrecomputedCookies = ( config: AuthConfig | undefined, logger: InternalLogger | undefined ) => { diff --git a/packages/core/src/config/hooks.ts b/packages/core/src/config/hooks.ts index 069bfce2..d144deca 100644 --- a/packages/core/src/config/hooks.ts +++ b/packages/core/src/config/hooks.ts @@ -12,6 +12,5 @@ export const onErrorHook: OnErrorHook = ({ error, context }) => { export const onRequestHook: OnRequestHook = ({ request, context }) => { const useSecure = isSecureConnection(request, context.trustedProxyHeaders) - // @ts-ignore - context.cookies = useSecure ? context.cookieConfig.trusted : context.cookieConfig.untrusted + context.cookies.store = useSecure ? context.cookies.precomputed.trusted : context.cookies.precomputed.untrusted } diff --git a/packages/core/src/config/router.ts b/packages/core/src/config/router.ts index fde868b8..cc782533 100644 --- a/packages/core/src/config/router.ts +++ b/packages/core/src/config/router.ts @@ -89,7 +89,7 @@ export const createAuthInstance = < return { handlers: router, - jose: config.context.jose, + jose: config.context.jose.instance, api: createAuthAPI, SignUpSchema>(config.context), } } diff --git a/packages/core/src/session/jose-manager.ts b/packages/core/src/session/jose-manager.ts index 33eecebe..054fefee 100644 --- a/packages/core/src/session/jose-manager.ts +++ b/packages/core/src/session/jose-manager.ts @@ -1,11 +1,11 @@ import { AuraAuthError } from "@/errors/aura-error.ts" -import type { JWTManager } from "@/@types/internal.ts" +import type { JoseManager } from "@/@types/internal.ts" import type { JoseInstance, User, JWTConfig } from "@/@types/index.ts" export const createJoseManager = ( config: JWTConfig | undefined, jose: JoseInstance -): JWTManager => { +): JoseManager => { const mode = config?.mode ?? "sealed" if (!["sealed", "signed", "encrypted"].includes(mode)) { diff --git a/packages/core/src/session/stateful/cancel-email-change.ts b/packages/core/src/session/stateful/cancel-email-change.ts index 834a09a9..67b847ab 100644 --- a/packages/core/src/session/stateful/cancel-email-change.ts +++ b/packages/core/src/session/stateful/cancel-email-change.ts @@ -1,14 +1,14 @@ import { AuraAuthError } from "@/errors/aura-error.ts" import { createHash } from "@/shared/crypto.ts" -import type { InternalStatefulContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const cancelEmailChange = ({ ctx }: InternalStatefulContext): SessionStrategy["cancelEmailChange"] => { - const { sessionConfig } = ctx +export const cancelEmailChange = (ctx: InternalContextForStateful): SessionStrategy["cancelEmailChange"] => { + const { session } = ctx return async ({ token, userId }): Promise => { const now = new Date() if (token) { const tokenHash = await createHash(token) - const verificationToken = await sessionConfig.adapter.getTokenByHash(tokenHash) + const verificationToken = await session.config.adapter.getTokenByHash(tokenHash) if ( !verificationToken || @@ -18,7 +18,7 @@ export const cancelEmailChange = ({ ctx }: InternalStatefulContext): SessionStra ) { throw new AuraAuthError({ code: "INVALID_EMAIL_CHANGE_TOKEN" }) } - const userTokens = await sessionConfig.adapter.getTokensByUserId(verificationToken.userId) + const userTokens = await session.config.adapter.getTokensByUserId(verificationToken.userId) const pending = userTokens.filter( (token) => (token.type === "email_change" || token.type === "email_change_cancel") && @@ -26,20 +26,20 @@ export const cancelEmailChange = ({ ctx }: InternalStatefulContext): SessionStra !token.revokedAt && token.expiresAt > now ) - await Promise.all(pending.map((t) => sessionConfig.adapter.revokeToken(t.id))) + await Promise.all(pending.map((t) => session.config.adapter.revokeToken(t.id))) return } if (!userId) { throw new AuraAuthError({ code: "USER_NOT_FOUND" }) } - const userIdTokens = await sessionConfig.adapter.getTokensByUserId(userId) + const userIdTokens = await session.config.adapter.getTokensByUserId(userId) const pending = userIdTokens.filter( (token) => (token.type === "email_change" || token.type === "email_change_cancel") && !token.revokedAt && token.expiresAt > now ) - await Promise.all(pending.map((token) => sessionConfig.adapter.revokeToken(token.id))) + await Promise.all(pending.map((token) => session.config.adapter.revokeToken(token.id))) } } diff --git a/packages/core/src/session/stateful/change-email.ts b/packages/core/src/session/stateful/change-email.ts index 64e8e544..17647934 100644 --- a/packages/core/src/session/stateful/change-email.ts +++ b/packages/core/src/session/stateful/change-email.ts @@ -1,20 +1,20 @@ import { AuraAuthError } from "@/errors/aura-error.ts" import { createHash, createSecretValue, verifyPassword } from "@/shared/crypto.ts" -import type { SessionStrategy, InternalStatefulContext } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const changeEmail = ({ ctx }: InternalStatefulContext): SessionStrategy["changeEmail"] => { - const { sessionConfig } = ctx +export const changeEmail = (ctx: InternalContextForStateful): SessionStrategy["changeEmail"] => { + const { session } = ctx return async ({ userId, currentPassword, newEmail, origin }) => { const normalizedNewEmail = newEmail.trim().toLowerCase() if (!normalizedNewEmail) { throw new AuraAuthError({ code: "INVALID_EMAIL_VALUE" }) } - const accounts = await sessionConfig.adapter.getAccountsByUserId(userId) + const accounts = await session.config.adapter.getAccountsByUserId(userId) const credentialAccount = accounts.find((acc) => acc.type === "credentials") if (!credentialAccount) { throw new AuraAuthError({ code: "NO_CREDENTIALS_ACCOUNT_FOUND" }) } - const credential = await sessionConfig.adapter.getCredentialAccount(credentialAccount.id) + const credential = await session.config.adapter.getCredentialAccount(credentialAccount.id) if (!credential) { throw new AuraAuthError({ code: "NO_CREDENTIALS_ACCOUNT_FOUND" }) } @@ -22,11 +22,11 @@ export const changeEmail = ({ ctx }: InternalStatefulContext): SessionStrategy[" if (!isMatch) { throw new AuraAuthError({ code: "INVALID_CURRENT_PASSWORD" }) } - const isEmailRegistered = await sessionConfig.adapter.getUserByEmail(normalizedNewEmail) + const isEmailRegistered = await session.config.adapter.getUserByEmail(normalizedNewEmail) if (isEmailRegistered) { throw new AuraAuthError({ code: "EMAIL_ALREADY_REGISTERED" }) } - const user = await sessionConfig.adapter.getUserById(userId) + const user = await session.config.adapter.getUserById(userId) if (!user) { throw new AuraAuthError({ code: "SILENT_USER_NOT_FOUND" }) } @@ -34,14 +34,14 @@ export const changeEmail = ({ ctx }: InternalStatefulContext): SessionStrategy[" throw new AuraAuthError({ code: "USER_EMAIL_NOT_FOUND" }) } await Promise.all([ - await sessionConfig.adapter.revokeAllVerificationTokens(userId, "email_change"), - await sessionConfig.adapter.revokeAllVerificationTokens(userId, "email_change_cancel"), + await session.config.adapter.revokeAllVerificationTokens(userId, "email_change"), + await session.config.adapter.revokeAllVerificationTokens(userId, "email_change_cancel"), ]) const confirmToken = createSecretValue() const confirmTokenHash = await createHash(confirmToken) const expiresIn = ctx.emailChange?.expiresIn ?? 1800 - await sessionConfig.adapter.createToken({ + await session.config.adapter.createToken({ userId, expiresAt: new Date(Date.now() + expiresIn * 1000), identifier: user.email, @@ -52,7 +52,7 @@ export const changeEmail = ({ ctx }: InternalStatefulContext): SessionStrategy[" const cancelToken = createSecretValue() const cancelTokenHash = await createHash(cancelToken) - await sessionConfig.adapter.createToken({ + await session.config.adapter.createToken({ userId, expiresAt: new Date(Date.now() + expiresIn * 1000), identifier: user.email, diff --git a/packages/core/src/session/stateful/change-password.ts b/packages/core/src/session/stateful/change-password.ts index 7d98aa4a..e21e3ec5 100644 --- a/packages/core/src/session/stateful/change-password.ts +++ b/packages/core/src/session/stateful/change-password.ts @@ -1,19 +1,19 @@ import { AuraAuthError } from "@/errors/aura-error.ts" import { hashPassword, verifyPassword } from "@/shared/crypto.ts" -import type { SessionStrategy, InternalStatefulContext } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const changePassword = ({ ctx }: InternalStatefulContext): SessionStrategy["changePassword"] => { - const { sessionConfig, credentials } = ctx +export const changePassword = (ctx: InternalContextForStateful): SessionStrategy["changePassword"] => { + const { session, credentials } = ctx const hashPasswordFn = credentials?.hash ?? hashPassword const verifyPasswordFn = credentials?.verify ?? verifyPassword return async ({ userId, currentPassword, newPassword }) => { - const accounts = await sessionConfig.adapter.getAccountsByUserId(userId) + const accounts = await session.config.adapter.getAccountsByUserId(userId) const credentialAccount = accounts.find((acc) => acc.type === "credentials") if (!credentialAccount) { throw new AuraAuthError({ code: "NO_CREDENTIALS_ACCOUNT_FOUND" }) } - const credential = await sessionConfig.adapter.getCredentialAccount(credentialAccount.id) + const credential = await session.config.adapter.getCredentialAccount(credentialAccount.id) if (!credential) { throw new AuraAuthError({ code: "NO_CREDENTIALS_ACCOUNT_FOUND" }) } @@ -22,11 +22,11 @@ export const changePassword = ({ ctx }: InternalStatefulContext): SessionStrateg throw new AuraAuthError({ code: "INVALID_CURRENT_PASSWORD" }) } const passwordHash = await hashPasswordFn(newPassword) - const revokeSessions = await sessionConfig.adapter.revokeAllSessions(userId, "password_changed") + const revokeSessions = await session.config.adapter.revokeAllSessions(userId, "password_changed") if (!revokeSessions || revokeSessions === 0) { throw new AuraAuthError({ code: "FAILED_TO_REVOKE_SESSIONS_AFTER_PASSWORD_CHANGE" }) } - const updatedCredentialAccount = await sessionConfig.adapter.updatePasswordHash(credentialAccount.id, passwordHash) + const updatedCredentialAccount = await session.config.adapter.updatePasswordHash(credentialAccount.id, passwordHash) if (!updatedCredentialAccount) { throw new AuraAuthError({ code: "INVALID_PASSWORD_HASH_IN_PASSWORD_CHANGE" }) } diff --git a/packages/core/src/session/stateful/create-session.ts b/packages/core/src/session/stateful/create-session.ts index 7cfc0c80..01275f79 100644 --- a/packages/core/src/session/stateful/create-session.ts +++ b/packages/core/src/session/stateful/create-session.ts @@ -1,11 +1,11 @@ import { AuraAuthError } from "@/errors/aura-error.ts" import { createHash, createSecretValue } from "@/shared/crypto.ts" -import { createDevice as __createDevice } from "@/shared/utils/session-strategy.ts" -import type { InternalStatefulContext, SessionStrategy } from "@/@types/internal.ts" +import { createDevice as createDeviceHelper } from "@/shared/utils/session-strategy.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const createSession = ({ ctx, cookies, cookieManager }: InternalStatefulContext): SessionStrategy["createSession"] => { - const { logger, sessionConfig } = ctx - const createDevice = __createDevice({ ctx, cookies, cookieManager }) +export const createSession = (ctx: InternalContextForStateful): SessionStrategy["createSession"] => { + const { logger, session: sessionConfig } = ctx + const createDevice = createDeviceHelper(ctx) return async ({ session, request }) => { logger?.log("STATEFUL_CREATE_SESSION_START", { @@ -57,7 +57,7 @@ export const createSession = ({ ctx, cookies, cookieManager }: InternalStatefulC }, }) - const expiresAt = new Date(Date.now() + (sessionConfig?.maxAge || 60 * 60 * 24 * 15) * 1000) + const expiresAt = new Date(Date.now() + (sessionConfig?.config.maxAge || 60 * 60 * 24 * 15) * 1000) logger?.log("STATEFUL_SESSION_EXPIRATION_SET", { structuredData: { expires_at: expiresAt?.toISOString(), @@ -68,7 +68,7 @@ export const createSession = ({ ctx, cookies, cookieManager }: InternalStatefulC const cryptoId = createSecretValue(32) const { sub: userId, email, image, name, ...attributes } = payload - let user = await sessionConfig.adapter.getUserById(userId as string) + let user = await sessionConfig.config.adapter.getUserById(userId as string) if (!user) { logger?.log("STATEFUL_USER_NOT_FOUND_CREATING", { structuredData: { @@ -76,7 +76,7 @@ export const createSession = ({ ctx, cookies, cookieManager }: InternalStatefulC reason: "user_not_found_creating_new", }, }) - user = await sessionConfig.adapter.createUser({ + user = await sessionConfig.config.adapter.createUser({ id: userId as string, name, email, @@ -96,7 +96,7 @@ export const createSession = ({ ctx, cookies, cookieManager }: InternalStatefulC reason: "user_exists_updating", }, }) - user = await sessionConfig.adapter.updateUser(userId as string, { + user = await sessionConfig.config.adapter.updateUser(userId as string, { name, email, image, @@ -112,7 +112,7 @@ export const createSession = ({ ctx, cookies, cookieManager }: InternalStatefulC const device = await createDevice(userId as string, request) - const dbSession = await sessionConfig.adapter.createSession({ + const dbSession = await sessionConfig.config.adapter.createSession({ id: cryptoId, userId: userId as string, deviceId: device.id, diff --git a/packages/core/src/session/stateful/destroy-session.ts b/packages/core/src/session/stateful/destroy-session.ts index 239bfdfb..60ce97ba 100644 --- a/packages/core/src/session/stateful/destroy-session.ts +++ b/packages/core/src/session/stateful/destroy-session.ts @@ -1,7 +1,7 @@ -import type { InternalStatefulContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const destroySession = ({ ctx, cookieManager }: InternalStatefulContext): SessionStrategy["destroySession"] => { - const { logger, sessionConfig } = ctx +export const destroySession = (ctx: InternalContextForStateful): SessionStrategy["destroySession"] => { + const { logger, session } = ctx return async ({ sessionByToken }) => { logger?.log("STATEFUL_DESTROY_SESSION_START", { @@ -10,8 +10,8 @@ export const destroySession = ({ ctx, cookieManager }: InternalStatefulContext): operation: "destroySession", }, }) - await sessionConfig.adapter.revokeSession(sessionByToken.id, "user_logout") - const clearedHeaders = cookieManager.clearAll() + await session.config.adapter.revokeSession(sessionByToken.id, "user_logout") + const clearedHeaders = ctx.cookies.manager.clearAll() logger?.log("STATEFUL_DESTROY_SESSION_SUCCESS", { structuredData: { cookies_cleared: true, diff --git a/packages/core/src/session/stateful/forgot-password.ts b/packages/core/src/session/stateful/forgot-password.ts index 5be65521..dd66862b 100644 --- a/packages/core/src/session/stateful/forgot-password.ts +++ b/packages/core/src/session/stateful/forgot-password.ts @@ -1,18 +1,18 @@ import { AuraAuthError } from "@/errors/aura-error.ts" import { createHash, createSecretValue } from "@/shared/crypto.ts" -import type { SessionStrategy, InternalStatefulContext } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const forgotPassword = ({ ctx }: InternalStatefulContext): SessionStrategy["forgotPassword"] => { - const { sessionConfig, passwordReset, basePath } = ctx +export const forgotPassword = (ctx: InternalContextForStateful): SessionStrategy["forgotPassword"] => { + const { session, passwordReset, basePath } = ctx return async ({ email, origin }) => { - const user = await sessionConfig.adapter.getUserByEmail(email) + const user = await session.config.adapter.getUserByEmail(email) if (!user) { throw new AuraAuthError({ code: "SILENT_USER_NOT_FOUND" }) } const token = createSecretValue() const tokenHash = await createHash(token) const expiresIn = passwordReset?.expiresIn ?? 1800 - await sessionConfig.adapter.createToken({ + await session.config.adapter.createToken({ userId: user.id, expiresAt: new Date(Date.now() + expiresIn * 1000), identifier: email, diff --git a/packages/core/src/session/stateful/get-provider-tokens.ts b/packages/core/src/session/stateful/get-provider-tokens.ts index d802584c..0f83fe07 100644 --- a/packages/core/src/session/stateful/get-provider-tokens.ts +++ b/packages/core/src/session/stateful/get-provider-tokens.ts @@ -3,10 +3,10 @@ import { handleApiError } from "@/shared/utils/api.ts" import { AuraAuthError, getErrorName } from "@/errors/aura-error.ts" import { refreshProviderToken } from "@/shared/utils/refresh-tokens.ts" import { secureApiHeaders, toUnionHeaders } from "@/shared/http/headers.ts" -import type { InternalStatefulContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const getProviderTokens = ({ ctx, cookieManager }: InternalStatefulContext): SessionStrategy["getProviderTokens"] => { - const { logger, sessionConfig } = ctx +export const getProviderTokens = (ctx: InternalContextForStateful): SessionStrategy["getProviderTokens"] => { + const { logger, session } = ctx return async ({ oauthId, provider, request, sessionByToken }) => { logger?.log("STATEFUL_GET_PROVIDER_TOKENS_START", { @@ -21,14 +21,20 @@ export const getProviderTokens = ({ ctx, cookieManager }: InternalStatefulContex const isExpired = Date.now() > sessionByToken.expiresAt.getTime() if (sessionByToken.status !== "active" || isExpired) { if (isExpired) { - await sessionConfig.adapter.revokeSession(sessionByToken.id, "user_logout") + await session.config.adapter.revokeSession(sessionByToken.id, "user_logout") } const { code, message, statusCode } = handleApiError( new AuraAuthError({ code: "SESSION_NOT_FOUND" }), "PROVIDER_TOKENS_ERROR", "Failed to get provider tokens" ) - return { success: false, error: { code, message }, tokens: null, headers: cookieManager.clear(), statusCode } + return { + success: false, + error: { code, message }, + tokens: null, + headers: ctx.cookies.manager.clear(), + statusCode, + } } logger?.log("STATEFUL_GET_PROVIDER_TOKENS_SESSION_FOUND", { @@ -38,9 +44,9 @@ export const getProviderTokens = ({ ctx, cookieManager }: InternalStatefulContex }, }) - const accounts = await sessionConfig.adapter.getAccountsByUserId(sessionByToken.userId) + const accounts = await session.config.adapter.getAccountsByUserId(sessionByToken.userId) const getAccount = accounts.find((account) => account.provider === oauthId && account.status === "active") - const oauthAccount = getAccount ? await sessionConfig.adapter.getOAuthAccount(getAccount?.id ?? "") : null + const oauthAccount = getAccount ? await session.config.adapter.getOAuthAccount(getAccount?.id ?? "") : null if (!oauthAccount) { logger?.log("STATEFUL_GET_PROVIDER_TOKENS_OAUTH_ACCOUNT_NOT_FOUND", { structuredData: { @@ -115,7 +121,7 @@ export const getProviderTokens = ({ ctx, cookieManager }: InternalStatefulContex }, }) - await sessionConfig.adapter.updateOAuthTokens(getAccount?.id as string, { + await session.config.adapter.updateOAuthTokens(getAccount?.id as string, { accountId: oauthAccount.accountId, accessToken: refreshedTokens.accessToken, refreshToken: refreshedTokens.refreshToken, diff --git a/packages/core/src/session/stateful/get-session.ts b/packages/core/src/session/stateful/get-session.ts index 553f4916..ff6f5ad1 100644 --- a/packages/core/src/session/stateful/get-session.ts +++ b/packages/core/src/session/stateful/get-session.ts @@ -4,18 +4,17 @@ import { AuraAuthError } from "@/errors/aura-error.ts" import { secureApiHeaders } from "@/shared/http/headers.ts" import { isInvalidSlidingThreshold } from "@/shared/assert.ts" import { calcStatefulExpiration, verifyDebounceLastActivity } from "@/shared/utils/session-strategy.ts" -import type { InternalStatefulContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" import type { User } from "@/@types/index.ts" -export const getSession = ({ - ctx, - cookieManager, -}: InternalStatefulContext): SessionStrategy["getSession"] => { - const { logger, sessionConfig } = ctx - const maxAge = sessionConfig?.maxAge ?? 60 * 60 * 24 * 15 - const strategy = sessionConfig?.expirationStrategy ?? "absolute" - const slidingThreshold = sessionConfig?.slidingThreshold - const touchThreshold = sessionConfig?.database?.touchInterval ?? 5 * 60 * 1000 +export const getSession = ( + ctx: InternalContextForStateful +): SessionStrategy["getSession"] => { + const { logger, session: sessionConfig } = ctx + const maxAge = sessionConfig.config?.maxAge ?? 60 * 60 * 24 * 15 + const strategy = sessionConfig.config?.expirationStrategy ?? "absolute" + const slidingThreshold = sessionConfig.config?.slidingThreshold + const touchThreshold = sessionConfig.config?.database?.touchInterval ?? 5 * 60 * 1000 if (isInvalidSlidingThreshold(slidingThreshold)) { throw new AuraAuthError({ code: "INVALID_SLIDING_THRESHOLD_CONFIG_VALUE" }) } @@ -29,7 +28,7 @@ export const getSession = ({ }) try { - const { sessionToken } = cookieManager.getCookie(headers) + const { sessionToken } = ctx.cookies.manager.getCookie(headers) logger?.log("STATEFUL_SESSION_TOKEN_EXTRACTED", { structuredData: { @@ -51,7 +50,7 @@ export const getSession = ({ } const tokenHash = await createHash(sessionToken) - const session = await sessionConfig.adapter.getSessionByToken(tokenHash) + const session = await sessionConfig.config.adapter.getSessionByToken(tokenHash) logger?.log("STATEFUL_SESSION_DB_LOOKUP", { structuredData: { session_found: Boolean(session), @@ -97,7 +96,7 @@ export const getSession = ({ }) return { session: null, - headers: cookieManager.clear(), + headers: ctx.cookies.manager.clear(), } } @@ -108,11 +107,11 @@ export const getSession = ({ expires_at: session.expiresAt.toISOString(), }, }) - await sessionConfig.adapter.revokeSession(session.id, "user_logout") + await sessionConfig.config.adapter.revokeSession(session.id, "user_logout") return { session: null, - headers: cookieManager.clear(), + headers: ctx.cookies.manager.clear(), } } @@ -149,11 +148,11 @@ export const getSession = ({ strategy, expiresAt: session.expiresAt, createdAt: session.createdAt, - maxDuration: sessionConfig.maxDuration, + maxDuration: sessionConfig.config.maxDuration, slidingThreshold, }) if (calc.action === "extend") { - await sessionConfig.adapter.updateSession(session.id, { + await sessionConfig.config.adapter.updateSession(session.id, { expiresAt: calc.expiresAt, lastActivityAt: new Date(), }) @@ -162,7 +161,7 @@ export const getSession = ({ if (calc.action === "touch") { const lastActivityAt = session.lastActivityAt.getTime() if (verifyDebounceLastActivity(now, lastActivityAt, touchThreshold)) { - await sessionConfig.adapter.touchSession(session.id, new Date(now)) + await sessionConfig.config.adapter.touchSession(session.id, new Date(now)) } } return { @@ -170,7 +169,7 @@ export const getSession = ({ user: parsedUser as DefaultUser, expires: effectiveExpiresAt.toISOString(), }, - headers: cookieManager.setCookie({ sessionToken }), + headers: ctx.cookies.manager.setCookie({ sessionToken }), } } catch (error) { logger?.log("STATEFUL_GET_SESSION_ERROR", { @@ -181,7 +180,7 @@ export const getSession = ({ }) return { session: null, - headers: cookieManager.clear(), + headers: ctx.cookies.manager.clear(), } } } diff --git a/packages/core/src/session/stateful/index.ts b/packages/core/src/session/stateful/index.ts index 2408a618..1d184fa5 100644 --- a/packages/core/src/session/stateful/index.ts +++ b/packages/core/src/session/stateful/index.ts @@ -22,10 +22,10 @@ import { cancelEmailChange } from "@/session/stateful/cancel-email-change.ts" import { isProviderConnected } from "@/session/stateful/is-provider-connected.ts" import { sendVerificationEmail } from "@/session/stateful/send-verification-email.ts" import type { User } from "@/@types/index.ts" -import type { SessionStrategy, InternalStatefulContext } from "@/@types/internal.ts" +import type { SessionStrategy, InternalContextForStateful } from "@/@types/internal.ts" export const createStatefulStrategy = ( - ctx: InternalStatefulContext + ctx: InternalContextForStateful ): SessionStrategy => { return { signUp: signUp(ctx), diff --git a/packages/core/src/session/stateful/is-provider-connected.ts b/packages/core/src/session/stateful/is-provider-connected.ts index 18ab13cf..b5f9dd43 100644 --- a/packages/core/src/session/stateful/is-provider-connected.ts +++ b/packages/core/src/session/stateful/is-provider-connected.ts @@ -1,8 +1,8 @@ import { getErrorName } from "@/errors/aura-error.ts" -import type { InternalStatefulContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const isProviderConnected = ({ ctx }: InternalStatefulContext): SessionStrategy["isProviderConnected"] => { - const { logger, sessionConfig } = ctx +export const isProviderConnected = (ctx: InternalContextForStateful): SessionStrategy["isProviderConnected"] => { + const { logger, session } = ctx return async ({ oauthId, sessionByToken }) => { logger?.log("OAUTH_ACCESS_TOKEN_REQUEST_INITIATED", { @@ -15,7 +15,7 @@ export const isProviderConnected = ({ ctx }: InternalStatefulContext): SessionSt const isExpired = Date.now() > sessionByToken.expiresAt.getTime() if (sessionByToken.status !== "active" || isExpired) { if (isExpired) { - await sessionConfig.adapter.revokeSession(sessionByToken.id, "account_suspended") + await session.config.adapter.revokeSession(sessionByToken.id, "account_suspended") } logger?.log("AUTH_SESSION_INVALID", { structuredData: { @@ -32,7 +32,7 @@ export const isProviderConnected = ({ ctx }: InternalStatefulContext): SessionSt }, }) - const accounts = await sessionConfig.adapter.getAccountsByUserId(sessionByToken.userId) + const accounts = await session.config.adapter.getAccountsByUserId(sessionByToken.userId) const account = accounts.find((acc) => acc.provider === oauthId) if (!account) { diff --git a/packages/core/src/session/stateful/oauth-callback.ts b/packages/core/src/session/stateful/oauth-callback.ts index 5f8cb111..4a443d2d 100644 --- a/packages/core/src/session/stateful/oauth-callback.ts +++ b/packages/core/src/session/stateful/oauth-callback.ts @@ -4,14 +4,14 @@ import { validateIDToken } from "@/shared/oidc/id-token.ts" import { getTrustedOrigins } from "@/config/get-trusted-origins.ts" import { createAccessToken, getUserInfo } from "@/shared/oauth2/index.ts" import { createCSRF, createHash, createSecretValue } from "@/shared/crypto.ts" -import { createDevice as __createDevice } from "@/shared/utils/session-strategy.ts" +import { createDevice as createDeviceHelper } from "@/shared/utils/session-strategy.ts" import { isOIDCProvider, resolveOpenIDProvider } from "@/shared/oidc/resolve-provider.ts" import { getOriginURL, isRelativeURL, isSameOrigin, isTrustedOrigin } from "@/shared/utils/get-origin-url.ts" -import type { InternalStatefulContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const oauthCallback = ({ ctx, cookies, cookieManager }: InternalStatefulContext): SessionStrategy["oauthCallback"] => { - const { logger, jose, oauth, sessionConfig } = ctx - const createDevice = __createDevice({ ctx, cookies, cookieManager }) +export const oauthCallback = (ctx: InternalContextForStateful): SessionStrategy["oauthCallback"] => { + const { logger, jose, oauth, session } = ctx + const createDevice = createDeviceHelper(ctx) return async ({ oauthId, request, params: { state, code } }) => { const oauthConfig = oauth[oauthId] @@ -20,7 +20,7 @@ export const oauthCallback = ({ ctx, cookies, cookieManager }: InternalStatefulC } const isOIDC = isOIDCProvider(oauthConfig) - const transaction = await sessionConfig.adapter.getOAuthTransactionByState(state) + const transaction = await session.config.adapter.getOAuthTransactionByState(state) if (!transaction) { logger?.log("OAUTH_PROTOCOL_ERROR", { @@ -47,7 +47,7 @@ export const oauthCallback = ({ ctx, cookies, cookieManager }: InternalStatefulC expires_at: transaction.expiresAt.toISOString(), }, }) - await sessionConfig.adapter.deleteExpiredOAuthTransactions() + await session.config.adapter.deleteExpiredOAuthTransactions() return Response.json( { type: "PROTOCOL", @@ -75,7 +75,7 @@ export const oauthCallback = ({ ctx, cookies, cookieManager }: InternalStatefulC ) } - await sessionConfig.adapter.consumeOAuthTransaction(state) + await session.config.adapter.consumeOAuthTransaction(state) const resolvedConfig = isOIDC ? await resolveOpenIDProvider(oauthConfig) : oauthConfig @@ -139,12 +139,12 @@ export const oauthCallback = ({ ctx, cookies, cookieManager }: InternalStatefulC } let userId: string - const user = await sessionConfig.adapter.getUserByEmail(userInfo.email) + const user = await session.config.adapter.getUserByEmail(userInfo.email) if (user) { userId = user.id const { sub: _, name, email, image, ...attributes } = userInfo - await sessionConfig.adapter.updateUser(userId, { + await session.config.adapter.updateUser(userId, { name, email, image, @@ -152,7 +152,7 @@ export const oauthCallback = ({ ctx, cookies, cookieManager }: InternalStatefulC }) } else { const { sub: _sub, email, image, name, ...attributes } = userInfo - const newUser = await sessionConfig.adapter.createUser({ + const newUser = await session.config.adapter.createUser({ id: crypto.randomUUID(), email: email, name: name, @@ -165,7 +165,7 @@ export const oauthCallback = ({ ctx, cookies, cookieManager }: InternalStatefulC } let accountId: string - const account = await sessionConfig.adapter.getAccountByProvider(oauthId, userInfo.sub) + const account = await session.config.adapter.getAccountByProvider(oauthId, userInfo.sub) if (account) { if (account.userId !== userId) { @@ -174,7 +174,7 @@ export const oauthCallback = ({ ctx, cookies, cookieManager }: InternalStatefulC } accountId = account.id - await sessionConfig.adapter.updateOAuthTokens(account.id, { + await session.config.adapter.updateOAuthTokens(account.id, { accessToken: accessToken.access_token, refreshToken: accessToken.refresh_token || null, idToken: accessToken.id_token || null, @@ -186,7 +186,7 @@ export const oauthCallback = ({ ctx, cookies, cookieManager }: InternalStatefulC : null, }) } else { - const newAccount = await sessionConfig.adapter.createAccount({ + const newAccount = await session.config.adapter.createAccount({ id: crypto.randomUUID(), userId, provider: oauthId, @@ -196,7 +196,7 @@ export const oauthCallback = ({ ctx, cookies, cookieManager }: InternalStatefulC }) accountId = newAccount.id - await sessionConfig.adapter.createOAuthAccount({ + await session.config.adapter.createOAuthAccount({ accountId, accessToken: accessToken.access_token, refreshToken: accessToken.refresh_token || null, @@ -214,7 +214,7 @@ export const oauthCallback = ({ ctx, cookies, cookieManager }: InternalStatefulC const sessionToken = createSecretValue(64) const tokenHash = await createHash(sessionToken) - await sessionConfig.adapter.createSession({ + await session.config.adapter.createSession({ id: crypto.randomUUID(), userId, deviceId: device.id, @@ -225,7 +225,7 @@ export const oauthCallback = ({ ctx, cookies, cookieManager }: InternalStatefulC tokenHash, }) - const csrfToken = await createCSRF(jose) + const csrfToken = await createCSRF(jose.instance) logger?.log("OAUTH_CALLBACK_SUCCESS", { structuredData: { @@ -236,8 +236,8 @@ export const oauthCallback = ({ ctx, cookies, cookieManager }: InternalStatefulC const headersBuilder = new HeadersBuilder() .setHeader("Location", transaction.redirectTo || "/") - .setCookie(cookies().sessionToken.name, sessionToken, cookies().sessionToken.attributes) - .setCookie(cookies().csrfToken.name, csrfToken, cookies().csrfToken.attributes) + .setCookie(ctx.cookies.store.sessionToken.name, sessionToken, ctx.cookies.store.sessionToken.attributes) + .setCookie(ctx.cookies.store.csrfToken.name, csrfToken, ctx.cookies.store.csrfToken.attributes) return Response.json({ oauth: oauthId }, { status: 302, headers: headersBuilder.toHeaders() }) } diff --git a/packages/core/src/session/stateful/refresh-session.ts b/packages/core/src/session/stateful/refresh-session.ts index f5066b87..7eea183d 100644 --- a/packages/core/src/session/stateful/refresh-session.ts +++ b/packages/core/src/session/stateful/refresh-session.ts @@ -1,13 +1,12 @@ import { createHash } from "@/shared/crypto.ts" import { getErrorName } from "@/errors/aura-error.ts" import type { Session, User } from "@/@types/index.ts" -import type { InternalStatefulContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const refreshSession = ({ - ctx, - cookieManager, -}: InternalStatefulContext): SessionStrategy["refreshSession"] => { - const { logger, sessionConfig } = ctx +export const refreshSession = ( + ctx: InternalContextForStateful +): SessionStrategy["refreshSession"] => { + const { logger, session: sessionConfig } = ctx return async ({ headers, session }) => { logger?.log("STATEFUL_REFRESH_SESSION_START", { @@ -18,7 +17,7 @@ export const refreshSession = ({ }) try { - const { sessionToken } = cookieManager.getCookie(headers) + const { sessionToken } = ctx.cookies.manager.getCookie(headers) logger?.log("STATEFUL_SESSION_TOKEN_EXTRACTED", { structuredData: { has_token: Boolean(sessionToken), @@ -32,11 +31,11 @@ export const refreshSession = ({ reason: "no_session_token_in_cookie", }, }) - return { session: null, headers: cookieManager.clear() } + return { session: null, headers: ctx.cookies.manager.clear() } } const tokenHash = await createHash(sessionToken) - const sessionByToken = await sessionConfig.adapter.getSessionByToken(tokenHash) + const sessionByToken = await sessionConfig.config.adapter.getSessionByToken(tokenHash) logger?.log("STATEFUL_SESSION_DB_LOOKUP", { structuredData: { session_found: Boolean(sessionByToken), @@ -51,11 +50,11 @@ export const refreshSession = ({ reason: "session_not_found_or_no_user", }, }) - return { session: null, headers: cookieManager.clear() } + return { session: null, headers: ctx.cookies.manager.clear() } } if (sessionByToken.status !== "active") { - return { session: null, headers: cookieManager.clear() } + return { session: null, headers: ctx.cookies.manager.clear() } } logger?.log("STATEFUL_SESSION_EXPIRATION_CHECK", { @@ -73,14 +72,14 @@ export const refreshSession = ({ expires_at: sessionByToken.expiresAt.toISOString(), }, }) - await sessionConfig.adapter.revokeSession(sessionByToken.id, "user_logout") + await sessionConfig.config.adapter.revokeSession(sessionByToken.id, "user_logout") logger?.log("STATEFUL_EXPIRED_SESSION_REVOKED", { structuredData: { session_id: sessionByToken.id, reason: "session_expired", }, }) - return { session: null, headers: cookieManager.clear() } + return { session: null, headers: ctx.cookies.manager.clear() } } const { attributes, ...spreadUser } = sessionByToken.user @@ -136,7 +135,7 @@ export const refreshSession = ({ if (sessionPayload && Object.keys(sessionPayload).length > 0) { const { sub: _sub, ...userUpdateFields } = validatedUser - await sessionConfig.adapter.updateUser(sessionByToken.userId, userUpdateFields as any) + await sessionConfig.config.adapter.updateUser(sessionByToken.userId, userUpdateFields as any) logger?.log("STATEFUL_USER_UPDATED_IN_DB", { structuredData: { user_id: sessionByToken.userId, @@ -154,7 +153,7 @@ export const refreshSession = ({ }, }) - await sessionConfig.adapter.updateSession(sessionByToken.id, { + await sessionConfig.config.adapter.updateSession(sessionByToken.id, { id: sessionByToken.id, userId: sessionByToken.userId, deviceId: sessionByToken.deviceId, @@ -172,7 +171,7 @@ export const refreshSession = ({ }, }) - await sessionConfig.adapter.touchSession(sessionByToken.id, new Date()) + await sessionConfig.config.adapter.touchSession(sessionByToken.id, new Date()) logger?.log("STATEFUL_SESSION_TOUCHED", { structuredData: { session_id: sessionByToken.id, @@ -193,7 +192,7 @@ export const refreshSession = ({ }, }) - return { session: updatedSession, headers: cookieManager.setCookie({ sessionToken }) } + return { session: updatedSession, headers: ctx.cookies.manager.setCookie({ sessionToken }) } } catch (error) { logger?.log("STATEFUL_REFRESH_SESSION_ERROR", { structuredData: { @@ -201,7 +200,7 @@ export const refreshSession = ({ error_message: error instanceof Error ? error.message : String(error), }, }) - return { session: null, headers: cookieManager.clear() } + return { session: null, headers: ctx.cookies.manager.clear() } } } } diff --git a/packages/core/src/session/stateful/refresh-userInfo.ts b/packages/core/src/session/stateful/refresh-userInfo.ts index 87297376..243247d8 100644 --- a/packages/core/src/session/stateful/refresh-userInfo.ts +++ b/packages/core/src/session/stateful/refresh-userInfo.ts @@ -1,11 +1,11 @@ -import { refreshSession as __refreshSession } from "@/session/stateful/refresh-session.ts" +import { refreshSession as refreshSessionHelper } from "@/session/stateful/refresh-session.ts" import type { User } from "@/@types/session.ts" -import type { InternalStatefulContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" export const refreshUserInfo = ( - ctx: InternalStatefulContext + ctx: InternalContextForStateful ): SessionStrategy["refreshUserInfo"] => { - const refreshSession = __refreshSession(ctx) + const refreshSession = refreshSessionHelper(ctx) return async ({ headers, user }) => { const value = await refreshSession({ headers, session: { user } }) diff --git a/packages/core/src/session/stateful/reset-password.ts b/packages/core/src/session/stateful/reset-password.ts index 34da56cb..270052a6 100644 --- a/packages/core/src/session/stateful/reset-password.ts +++ b/packages/core/src/session/stateful/reset-password.ts @@ -1,12 +1,12 @@ import { AuraAuthError } from "@/errors/aura-error.ts" import { createHash, hashPassword } from "@/shared/crypto.ts" -import type { SessionStrategy, InternalStatefulContext } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const resetPassword = ({ ctx }: InternalStatefulContext): SessionStrategy["resetPassword"] => { - const { sessionConfig } = ctx +export const resetPassword = (ctx: InternalContextForStateful): SessionStrategy["resetPassword"] => { + const { session } = ctx return async ({ token, newPassword }) => { const tokenHash = await createHash(token) - const verificationToken = await sessionConfig.adapter.getTokenByHash(tokenHash) + const verificationToken = await session.config.adapter.getTokenByHash(tokenHash) if ( !verificationToken || @@ -17,7 +17,7 @@ export const resetPassword = ({ ctx }: InternalStatefulContext): SessionStrategy throw new AuraAuthError({ code: "INVALID_RESET_PASSWORD_TOKEN" }) } - const accounts = await sessionConfig.adapter.getAccountsByUserId(verificationToken.userId) + const accounts = await session.config.adapter.getAccountsByUserId(verificationToken.userId) const credentialAccount = accounts.find((acc) => acc.type === "credentials") if (!credentialAccount) { @@ -28,8 +28,8 @@ export const resetPassword = ({ ctx }: InternalStatefulContext): SessionStrategy * @todo should support a custom password hashing function, similar to the signInCredentials API ? */ const passwordHash = await hashPassword(newPassword) - await sessionConfig.adapter.updatePasswordHash(credentialAccount.id, passwordHash) - await sessionConfig.adapter.revokeToken(verificationToken.id) - await sessionConfig.adapter.revokeAllSessions(verificationToken.userId, "password_changed") + await session.config.adapter.updatePasswordHash(credentialAccount.id, passwordHash) + await session.config.adapter.revokeToken(verificationToken.id) + await session.config.adapter.revokeAllSessions(verificationToken.userId, "password_changed") } } diff --git a/packages/core/src/session/stateful/revoke-session.ts b/packages/core/src/session/stateful/revoke-session.ts index bca67a47..654a4a20 100644 --- a/packages/core/src/session/stateful/revoke-session.ts +++ b/packages/core/src/session/stateful/revoke-session.ts @@ -1,11 +1,11 @@ import { AuraAuthError } from "@/errors/aura-error.ts" -import type { InternalStatefulContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" /** * Is the function beeng used ? */ -export const revokeSession = ({ ctx }: InternalStatefulContext): SessionStrategy["revokeSession"] => { - const { logger, sessionConfig } = ctx +export const revokeSession = (ctx: InternalContextForStateful): SessionStrategy["revokeSession"] => { + const { logger, session } = ctx return async ({ sessionId }) => { logger?.log("STATEFUL_REVOKE_SESSION_START", { @@ -26,7 +26,7 @@ export const revokeSession = ({ ctx }: InternalStatefulContext): SessionStrategy throw new AuraAuthError({ code: "INVALID_USER_INFO" }) } - await sessionConfig.adapter.revokeSession(sessionId, "user_logout") + await session.config.adapter.revokeSession(sessionId, "user_logout") logger?.log("STATEFUL_REVOKE_SESSION_SUCCESS", { structuredData: { diff --git a/packages/core/src/session/stateful/revoke-token.ts b/packages/core/src/session/stateful/revoke-token.ts index 9eb599b3..d57782c7 100644 --- a/packages/core/src/session/stateful/revoke-token.ts +++ b/packages/core/src/session/stateful/revoke-token.ts @@ -3,10 +3,10 @@ import { getExpiredCookie } from "@/shared/http/cookie.ts" import { AuraAuthError, getErrorName } from "@/errors/aura-error.ts" import { secureApiHeaders, toUnionHeaders } from "@/shared/http/headers.ts" import { revokeProviderToken } from "@/shared/utils/revoke-token.ts" -import type { InternalStatefulContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const revokeToken = ({ ctx }: InternalStatefulContext): SessionStrategy["revokeToken"] => { - const { logger, sessionConfig, cookies } = ctx +export const revokeToken = (ctx: InternalContextForStateful): SessionStrategy["revokeToken"] => { + const { logger, session, cookies } = ctx return async ({ oauthId, provider, headers, disconnect, sessionByToken }) => { logger?.log("OAUTH_ACCESS_TOKEN_REQUEST_INITIATED", { @@ -21,7 +21,7 @@ export const revokeToken = ({ ctx }: InternalStatefulContext): SessionStrategy[" const isExpired = Date.now() > sessionByToken.expiresAt.getTime() if (sessionByToken.status !== "active" || isExpired) { if (isExpired) { - await sessionConfig.adapter.revokeSession(sessionByToken.id, "user_logout") + await session.config.adapter.revokeSession(sessionByToken.id, "user_logout") } logger?.log("AUTH_SESSION_INVALID", { structuredData: { @@ -38,7 +38,7 @@ export const revokeToken = ({ ctx }: InternalStatefulContext): SessionStrategy[" }, }) - const accounts = await sessionConfig.adapter.getAccountsByUserId(sessionByToken.userId) + const accounts = await session.config.adapter.getAccountsByUserId(sessionByToken.userId) const account = accounts.find((account) => account.provider === oauthId) if (!account) { logger?.log("OAUTH_UNLINKED_ACCOUNT_ERROR", { @@ -50,7 +50,7 @@ export const revokeToken = ({ ctx }: InternalStatefulContext): SessionStrategy[" throw new AuraAuthError({ code: "OAUTH_UNLINKED_ACCOUNT_ERROR" }) } - const oauthAccount = await sessionConfig.adapter.getOAuthAccount(account?.id) + const oauthAccount = await session.config.adapter.getOAuthAccount(account?.id) if (!oauthAccount) { logger?.log("OAUTH_UNLINKED_ACCOUNT_ERROR", { structuredData: { @@ -73,7 +73,7 @@ export const revokeToken = ({ ctx }: InternalStatefulContext): SessionStrategy[" }) } - await sessionConfig.adapter.updateAccountStatus(oauthAccount.accountId, "unlinked") + await session.config.adapter.updateAccountStatus(oauthAccount.accountId, "unlinked") logger?.log("OAUTH_ACCESS_TOKEN_SUCCESS", { structuredData: { @@ -82,9 +82,9 @@ export const revokeToken = ({ ctx }: InternalStatefulContext): SessionStrategy[" }, }) - const cookieName = `${cookies.accessToken.name}.${oauthId}` + const cookieName = `${cookies.store.accessToken.name}.${oauthId}` const builder = new HeadersBuilder(secureApiHeaders) - .setCookie(cookieName, "", getExpiredCookie(cookies.accessToken.attributes)) + .setCookie(cookieName, "", getExpiredCookie(cookies.store.accessToken.attributes)) .toHeaders() return toUnionHeaders(builder, headers) } catch (error) { diff --git a/packages/core/src/session/stateful/send-magic-link.ts b/packages/core/src/session/stateful/send-magic-link.ts index 5bf62b99..20dae730 100644 --- a/packages/core/src/session/stateful/send-magic-link.ts +++ b/packages/core/src/session/stateful/send-magic-link.ts @@ -1,16 +1,16 @@ import { AuraAuthError } from "@/errors/aura-error.ts" import { createHash, createSecretValue } from "@/shared/crypto.ts" -import type { SessionStrategy, InternalStatefulContext } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const sendMagicLink = ({ ctx }: InternalStatefulContext): SessionStrategy["sendMagicLink"] => { - const { sessionConfig } = ctx +export const sendMagicLink = (ctx: InternalContextForStateful): SessionStrategy["sendMagicLink"] => { + const { session } = ctx return async ({ email, origin, allowSignUp }) => { - let user = await sessionConfig.adapter.getUserByEmail(email) + let user = await session.config.adapter.getUserByEmail(email) if (!user) { if (!allowSignUp) { throw new AuraAuthError({ code: "SIGNUP_NOT_ALLOWED_FOR_MAGIC_LINK" }) } - user = await sessionConfig.adapter.createUser({ + user = await session.config.adapter.createUser({ email, emailVerifiedAt: null, }) @@ -21,12 +21,12 @@ export const sendMagicLink = ({ ctx }: InternalStatefulContext): SessionStrategy throw new AuraAuthError({ code: "SILENT_USER_NOT_FOUND" }) } - await sessionConfig.adapter.revokeAllVerificationTokens(user.id, "magic_link") + await session.config.adapter.revokeAllVerificationTokens(user.id, "magic_link") const token = createSecretValue() const tokenHash = await createHash(token) const expiresIn = ctx.magicLinks?.expiresIn ?? 1800 - await sessionConfig.adapter.createToken({ + await session.config.adapter.createToken({ userId: user.id, expiresAt: new Date(Date.now() + expiresIn * 1000), identifier: user.email, diff --git a/packages/core/src/session/stateful/send-verification-email.ts b/packages/core/src/session/stateful/send-verification-email.ts index e8e33b57..0dc8c3cb 100644 --- a/packages/core/src/session/stateful/send-verification-email.ts +++ b/packages/core/src/session/stateful/send-verification-email.ts @@ -1,18 +1,18 @@ import { AuraAuthError } from "@/errors/aura-error.ts" import { createHash, createSecretValue } from "@/shared/crypto.ts" -import type { SessionStrategy, InternalStatefulContext } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const sendVerificationEmail = ({ ctx }: InternalStatefulContext): SessionStrategy["sendVerificationEmail"] => { - const { sessionConfig, emailVerification, basePath } = ctx +export const sendVerificationEmail = (ctx: InternalContextForStateful): SessionStrategy["sendVerificationEmail"] => { + const { session, emailVerification, basePath } = ctx return async ({ email, origin }) => { - const user = await sessionConfig.adapter.getUserByEmail(email) + const user = await session.config.adapter.getUserByEmail(email) if (!user) { throw new AuraAuthError({ code: "SILENT_USER_NOT_FOUND" }) } const token = createSecretValue() const tokenHash = await createHash(token) const expiresIn = emailVerification?.expiresIn ?? 1800 - await sessionConfig.adapter.createToken({ + await session.config.adapter.createToken({ userId: user.id, expiresAt: new Date(Date.now() + expiresIn * 1000), identifier: email, diff --git a/packages/core/src/session/stateful/sign-in-credentials.ts b/packages/core/src/session/stateful/sign-in-credentials.ts index e38e8508..03ebd81c 100644 --- a/packages/core/src/session/stateful/sign-in-credentials.ts +++ b/packages/core/src/session/stateful/sign-in-credentials.ts @@ -1,15 +1,11 @@ import { AuraAuthError } from "@/errors/aura-error.ts" import { createHash, createSecretValue } from "@/shared/crypto.ts" -import { createDevice as __createDevice } from "@/shared/utils/session-strategy.ts" -import type { InternalStatefulContext, SessionStrategy } from "@/@types/internal.ts" +import { createDevice as createDeviceHelper } from "@/shared/utils/session-strategy.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const signInCredentials = ({ - ctx, - cookies, - cookieManager, -}: InternalStatefulContext): SessionStrategy["signInCredentials"] => { - const { logger, sessionConfig } = ctx - const createDevice = __createDevice({ ctx, cookies, cookieManager }) +export const signInCredentials = (ctx: InternalContextForStateful): SessionStrategy["signInCredentials"] => { + const { logger, session } = ctx + const createDevice = createDeviceHelper(ctx) return async ({ payload, request }) => { logger?.log("STATEFUL_SIGN_IN_CREDENTIALS_START", { @@ -37,7 +33,7 @@ export const signInCredentials = ({ const { sub } = validatedPayload - const user = await sessionConfig.adapter.getUserById(sub) + const user = await session.config.adapter.getUserById(sub) if (!user) { throw new AuraAuthError({ code: "AUTH_CREDENTIALS_INVALID" }) } @@ -66,7 +62,7 @@ export const signInCredentials = ({ }, }) - const dbSession = await sessionConfig.adapter.createSession({ + const dbSession = await session.config.adapter.createSession({ id: createSecretValue(32), userId: user.id, deviceId: device.id, diff --git a/packages/core/src/session/stateful/sign-in.ts b/packages/core/src/session/stateful/sign-in.ts index 5f863641..c3018c98 100644 --- a/packages/core/src/session/stateful/sign-in.ts +++ b/packages/core/src/session/stateful/sign-in.ts @@ -4,10 +4,10 @@ import { createOIDCAuthorizationURL } from "@/shared/oidc/authorization-url.ts" import { createFingerprint, getDeviceInfo } from "@/shared/http/request-metadata.ts" import { isOIDCProvider, resolveOpenIDProvider } from "@/shared/oidc/resolve-provider.ts" import { createAuthorizationURL, createRedirectTo, createRedirectURI } from "@/shared/oauth2/authorization.ts" -import type { InternalStatefulContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const signIn = ({ ctx }: InternalStatefulContext): SessionStrategy["signIn"] => { - const { logger, sessionConfig } = ctx +export const signIn = (ctx: InternalContextForStateful): SessionStrategy["signIn"] => { + const { logger, session } = ctx return async ({ oauthId, provider, request, redirectTo }) => { const redirectURI = await createRedirectURI(request, oauthId, ctx) @@ -52,7 +52,7 @@ export const signIn = ({ ctx }: InternalStatefulContext): SessionStrategy["signI const fingerprint = await createFingerprint(request) const expiresAt = new Date(Date.now() + 10 * 60 * 1000) - await sessionConfig.adapter.createOAuthTransaction({ + await session.config.adapter.createOAuthTransaction({ id: crypto.randomUUID(), provider: oauthId, state, diff --git a/packages/core/src/session/stateful/sign-up.ts b/packages/core/src/session/stateful/sign-up.ts index 1a59a9bd..708f0987 100644 --- a/packages/core/src/session/stateful/sign-up.ts +++ b/packages/core/src/session/stateful/sign-up.ts @@ -1,14 +1,14 @@ import { AuraAuthError } from "@/errors/aura-error.ts" import { createHash, createSecretValue, hashPassword } from "@/shared/crypto.ts" -import { createDevice as __createDevice } from "@/shared/utils/session-strategy.ts" -import type { InternalStatefulContext, SessionStrategy } from "@/@types/internal.ts" +import { createDevice as createDeviceHelper } from "@/shared/utils/session-strategy.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" /** * @todo Add transaction support for the signUp process to ensure atomicity and rollback in case of errors. */ -export const signUp = ({ ctx, cookies, cookieManager }: InternalStatefulContext): SessionStrategy["signUp"] => { - const { logger, sessionConfig } = ctx - const createDevice = __createDevice({ ctx, cookies, cookieManager }) +export const signUp = (ctx: InternalContextForStateful): SessionStrategy["signUp"] => { + const { logger, session } = ctx + const createDevice = createDeviceHelper(ctx) return async ({ payload, request }): Promise => { logger?.log("STATEFUL_CREATE_SESSION_START", { @@ -42,14 +42,14 @@ export const signUp = ({ ctx, cookies, cookieManager }: InternalStatefulContext) const email = typeof rawEmail === "string" ? rawEmail.trim().toLowerCase() : rawEmail if (email) { - const getEmail = await sessionConfig.adapter.getUserByEmail(email) + const getEmail = await session.config.adapter.getUserByEmail(email) if (getEmail) { throw new AuraAuthError({ code: "EMAIL_ALREADY_REGISTERED" }) } } const userId = createSecretValue(32) - const user = await sessionConfig.adapter.createUser({ + const user = await session.config.adapter.createUser({ id: userId, name, email, @@ -66,7 +66,7 @@ export const signUp = ({ ctx, cookies, cookieManager }: InternalStatefulContext) }, }) - const account = await sessionConfig.adapter.createAccount({ + const account = await session.config.adapter.createAccount({ id: createSecretValue(32), userId: user.id, provider: "credentials", @@ -80,7 +80,7 @@ export const signUp = ({ ctx, cookies, cookieManager }: InternalStatefulContext) throw new AuraAuthError({ code: "AUTH_CREDENTIALS_INVALID" }) } const passwordHash = await hashPassword(password) - await sessionConfig.adapter.createCredentialAccount({ + await session.config.adapter.createCredentialAccount({ accountId: account.id, passwordHash, }) @@ -109,7 +109,7 @@ export const signUp = ({ ctx, cookies, cookieManager }: InternalStatefulContext) }, }) - const dbSession = await sessionConfig.adapter.createSession({ + const dbSession = await session.config.adapter.createSession({ id: createSecretValue(32), userId: user.id, deviceId: device.id, diff --git a/packages/core/src/session/stateful/verify-email-change.ts b/packages/core/src/session/stateful/verify-email-change.ts index 8816f3c4..80fef5d0 100644 --- a/packages/core/src/session/stateful/verify-email-change.ts +++ b/packages/core/src/session/stateful/verify-email-change.ts @@ -1,12 +1,12 @@ import { AuraAuthError } from "@/errors/aura-error.ts" import { createHash, timingSafeEqual } from "@/shared/crypto.ts" -import type { SessionStrategy, InternalStatefulContext } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const verifyEmailChange = ({ ctx }: InternalStatefulContext): SessionStrategy["verifyEmailChange"] => { - const { sessionConfig } = ctx +export const verifyEmailChange = (ctx: InternalContextForStateful): SessionStrategy["verifyEmailChange"] => { + const { session } = ctx return async ({ userId, sessionId, token }) => { const tokenHash = await createHash(token) - const verifiedToken = await sessionConfig.adapter.getTokenByHash(tokenHash) + const verifiedToken = await session.config.adapter.getTokenByHash(tokenHash) if ( !verifiedToken || verifiedToken.type !== "email_change" || @@ -19,11 +19,11 @@ export const verifyEmailChange = ({ ctx }: InternalStatefulContext): SessionStra if (!timingSafeEqual(userId, verifiedToken.userId)) { throw new AuraAuthError({ code: "INVALID_EMAIL_CHANGE_TOKEN" }) } - await sessionConfig.adapter.revokeToken(verifiedToken.id) - await sessionConfig.adapter.updateUser(userId, { + await session.config.adapter.revokeToken(verifiedToken.id) + await session.config.adapter.updateUser(userId, { email: verifiedToken.newIdentifier, emailVerifiedAt: new Date(), }) - await sessionConfig.adapter.revokeAllSessions(userId, "email_changed", sessionId) + await session.config.adapter.revokeAllSessions(userId, "email_changed", sessionId) } } diff --git a/packages/core/src/session/stateful/verify-email.ts b/packages/core/src/session/stateful/verify-email.ts index 03d743ae..c7c93add 100644 --- a/packages/core/src/session/stateful/verify-email.ts +++ b/packages/core/src/session/stateful/verify-email.ts @@ -1,12 +1,12 @@ import { createHash } from "@/shared/crypto.ts" import { AuraAuthError } from "@/errors/aura-error.ts" -import type { SessionStrategy, InternalStatefulContext } from "@/@types/internal.ts" +import type { SessionStrategy, InternalContextForStateful } from "@/@types/internal.ts" -export const verifyEmail = ({ ctx }: InternalStatefulContext): SessionStrategy["verifyEmail"] => { - const { sessionConfig } = ctx +export const verifyEmail = (ctx: InternalContextForStateful): SessionStrategy["verifyEmail"] => { + const { session } = ctx return async ({ token }) => { const tokenHash = await createHash(token) - const verificationToken = await sessionConfig.adapter.getTokenByHash(tokenHash) + const verificationToken = await session.config.adapter.getTokenByHash(tokenHash) if ( !verificationToken || verificationToken.type !== "email_verification" || @@ -16,10 +16,10 @@ export const verifyEmail = ({ ctx }: InternalStatefulContext): SessionStrategy[" throw new AuraAuthError({ code: "INVALID_EMAIL_VERIFICATION_TOKEN" }) } - await sessionConfig.adapter.updateUser(verificationToken.userId, { + await session.config.adapter.updateUser(verificationToken.userId, { emailVerifiedAt: new Date(), }) - await sessionConfig.adapter.revokeToken(verificationToken.id) + await session.config.adapter.revokeToken(verificationToken.id) } } diff --git a/packages/core/src/session/stateful/verify-magic-link.ts b/packages/core/src/session/stateful/verify-magic-link.ts index 246e68b4..b09f1cf9 100644 --- a/packages/core/src/session/stateful/verify-magic-link.ts +++ b/packages/core/src/session/stateful/verify-magic-link.ts @@ -1,40 +1,40 @@ import { AuraAuthError } from "@/errors/aura-error.ts" import { createHash, createSecretValue } from "@/shared/crypto.ts" import { createDevice as createDeviceRegistry } from "@/shared/utils/session-strategy.ts" -import type { SessionStrategy, InternalStatefulContext } from "@/@types/internal.ts" +import type { InternalContextForStateful, SessionStrategy } from "@/@types/internal.ts" -export const verifyMagicLink = ({ ctx, cookies, cookieManager }: InternalStatefulContext): SessionStrategy["verifyMagicLink"] => { - const { sessionConfig } = ctx - const createDevice = createDeviceRegistry({ ctx, cookies, cookieManager }) +export const verifyMagicLink = (ctx: InternalContextForStateful): SessionStrategy["verifyMagicLink"] => { + const { session } = ctx + const createDevice = createDeviceRegistry(ctx) return async ({ token, request }) => { const now = new Date() const tokenHash = await createHash(token) - const verifiedToken = await sessionConfig.adapter.getTokenByHash(tokenHash) + const verifiedToken = await session.config.adapter.getTokenByHash(tokenHash) if (!verifiedToken || verifiedToken.type !== "magic_link" || verifiedToken.expiresAt < now || verifiedToken.revokedAt) { throw new AuraAuthError({ code: "INVALID_MAGIC_LINK_TOKEN" }) } - const isTokenRevoked = await sessionConfig.adapter.revokeToken(verifiedToken.id) + const isTokenRevoked = await session.config.adapter.revokeToken(verifiedToken.id) if (!isTokenRevoked) { throw new AuraAuthError({ code: "INVALID_MAGIC_LINK_TOKEN" }) } - const user = await sessionConfig.adapter.getUserById(verifiedToken.userId) + const user = await session.config.adapter.getUserById(verifiedToken.userId) if (!user || user.status !== "active" || user.email !== verifiedToken.identifier) { throw new AuraAuthError({ code: "SILENT_USER_NOT_FOUND" }) } - await sessionConfig.adapter.revokeAllVerificationTokens(verifiedToken.userId, "magic_link") + await session.config.adapter.revokeAllVerificationTokens(verifiedToken.userId, "magic_link") - await sessionConfig.adapter.updateUser(verifiedToken.userId, { + await session.config.adapter.updateUser(verifiedToken.userId, { emailVerifiedAt: new Date(), }) - const existingAccounts = await sessionConfig.adapter.getAccountsByUserId(verifiedToken.userId) + const existingAccounts = await session.config.adapter.getAccountsByUserId(verifiedToken.userId) const hasMagicLinkAccount = existingAccounts.some((acc) => acc.provider === "magic_link") if (!hasMagicLinkAccount) { - await sessionConfig.adapter.createAccount({ + await session.config.adapter.createAccount({ userId: verifiedToken.userId, provider: "magic_link", status: "active", @@ -44,13 +44,13 @@ export const verifyMagicLink = ({ ctx, cookies, cookieManager }: InternalStatefu } const device = await createDevice(verifiedToken.userId, request) - const maxAge = sessionConfig.maxAge ?? 60 * 60 * 24 * 15 + const maxAge = session.config.maxAge ?? 60 * 60 * 24 * 15 const expiresAt = new Date(Date.now() + maxAge * 1000) const sessionToken = createSecretValue(64) const sessionTokenHash = await createHash(sessionToken) - await sessionConfig.adapter.createSession({ + await session.config.adapter.createSession({ id: createSecretValue(32), userId: verifiedToken.userId, deviceId: device.id, diff --git a/packages/core/src/session/stateless/create-session.ts b/packages/core/src/session/stateless/create-session.ts index 582e41dd..1d1797ec 100644 --- a/packages/core/src/session/stateless/create-session.ts +++ b/packages/core/src/session/stateless/create-session.ts @@ -1,10 +1,10 @@ import type { User } from "@/@types/index.ts" -import type { InternalStatelessContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateless, SessionStrategy } from "@/@types/internal.ts" -export const createSession = ({ - ctx, -}: InternalStatelessContext): SessionStrategy["createSession"] => { - const { logger, identity, jwtManager } = ctx +export const createSession = ( + ctx: InternalContextForStateless +): SessionStrategy["createSession"] => { + const { logger, identity } = ctx return async ({ session }) => { if (identity.skipValidation) { logger?.log("IDENTITY_VALIDATION_DISABLED", { @@ -14,6 +14,6 @@ export const createSession = ({ }) } const payload = identity.skipValidation ? session : await identity.schemaRegistry.parse(session) - return jwtManager.createToken(payload as unknown as DefaultUser) + return ctx.jose.manager.createToken(payload as unknown as DefaultUser) } } diff --git a/packages/core/src/session/stateless/destroy-session.ts b/packages/core/src/session/stateless/destroy-session.ts index cd7b6771..03102f43 100644 --- a/packages/core/src/session/stateless/destroy-session.ts +++ b/packages/core/src/session/stateless/destroy-session.ts @@ -1,7 +1,7 @@ -import type { InternalStatelessContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateless, SessionStrategy } from "@/@types/internal.ts" -export const destroySession = ({ cookieManager }: InternalStatelessContext): SessionStrategy["destroySession"] => { +export const destroySession = (ctx: InternalContextForStateless): SessionStrategy["destroySession"] => { return async () => { - return cookieManager.clearAll() + return ctx.cookies.manager.clearAll() } } diff --git a/packages/core/src/session/stateless/get-provider-tokens.ts b/packages/core/src/session/stateless/get-provider-tokens.ts index 0318e8fa..56133c47 100644 --- a/packages/core/src/session/stateless/get-provider-tokens.ts +++ b/packages/core/src/session/stateless/get-provider-tokens.ts @@ -6,10 +6,10 @@ import { handleApiError } from "@/shared/utils/api.ts" import { AuraAuthError } from "@/errors/aura-error.ts" import { secureApiHeaders } from "@/shared/http/headers.ts" import { refreshProviderToken } from "@/shared/utils/refresh-tokens.ts" -import type { InternalStatelessContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateless, SessionStrategy } from "@/@types/internal.ts" -export const getProviderTokens = ({ ctx, cookies }: InternalStatelessContext): SessionStrategy["getProviderTokens"] => { - const { logger, identity, jwtManager: jwt } = ctx +export const getProviderTokens = (ctx: InternalContextForStateless): SessionStrategy["getProviderTokens"] => { + const { logger, identity, jose } = ctx return async ({ oauthId, provider, request }) => { logger?.log("STATELESS_GET_PROVIDER_TOKENS_START", { @@ -27,7 +27,7 @@ export const getProviderTokens = ({ ctx, cookies }: InternalStatelessContext): S }, }) - const cookieName = `${cookies().accessToken.name}.${oauthId}` + const cookieName = `${ctx.cookies.store.accessToken.name}.${oauthId}` const cookie = getCookie(request, cookieName) if (!cookie) { @@ -53,7 +53,7 @@ export const getProviderTokens = ({ ctx, cookies }: InternalStatelessContext): S }, }) - const decodedToken = await jwt.verifyToken(cookie) + const decodedToken = await jose.manager.verifyToken(cookie) const tokens = await identity.schemaRegistry.parseOAuthTokens(decodedToken) logger?.log("STATELESS_GET_PROVIDER_TOKENS_TOKENS_DECODED", { @@ -95,9 +95,9 @@ export const getProviderTokens = ({ ctx, cookies }: InternalStatelessContext): S }, }) - const encodedTokens = await jwt.createToken(refreshedTokens as any) + const encodedTokens = await jose.manager.createToken(refreshedTokens as any) const builder = new HeadersBuilder(secureApiHeaders) - .setCookie(cookieName, encodedTokens, cookies().accessToken.attributes) + .setCookie(cookieName, encodedTokens, ctx.cookies.store.accessToken.attributes) .toHeaders() logger?.log("STATELESS_GET_PROVIDER_TOKENS_COOKIE_UPDATED", { diff --git a/packages/core/src/session/stateless/get-session.ts b/packages/core/src/session/stateless/get-session.ts index a398a68b..f6bf9b9f 100644 --- a/packages/core/src/session/stateless/get-session.ts +++ b/packages/core/src/session/stateless/get-session.ts @@ -4,17 +4,16 @@ import { secureApiHeaders } from "@/shared/http/headers.ts" import { isInvalidSlidingThreshold } from "@/shared/assert.ts" import { calcStatelessExpiration } from "@/shared/utils/session-strategy.ts" import type { Session, User } from "@/@types/index.ts" -import type { InternalStatelessContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateless, SessionStrategy } from "@/@types/internal.ts" -export const getSession = ({ - ctx, - cookieManager, -}: InternalStatelessContext): SessionStrategy["getSession"] => { - const { logger, identity, jwtManager, sessionConfig } = ctx +export const getSession = ( + ctx: InternalContextForStateless +): SessionStrategy["getSession"] => { + const { logger, identity, jose, session } = ctx - const maxAge = sessionConfig?.maxAge ?? sessionConfig?.jwt?.maxAge ?? 60 * 60 * 24 * 15 - const strategy = sessionConfig?.expirationStrategy ?? sessionConfig?.jwt?.expirationStrategy ?? "absolute" - const slidingThreshold = sessionConfig?.slidingThreshold + const maxAge = session?.config?.maxAge ?? session?.config?.jwt?.maxAge ?? 60 * 60 * 24 * 15 + const strategy = session?.config?.expirationStrategy ?? session?.config?.jwt?.expirationStrategy ?? "absolute" + const slidingThreshold = session?.config?.slidingThreshold if (isInvalidSlidingThreshold(slidingThreshold)) { throw new AuraAuthError({ code: "INVALID_SLIDING_THRESHOLD_CONFIG_VALUE" }) } @@ -22,10 +21,10 @@ export const getSession = ({ return async ({ headers }) => { const newHeaders = new Headers(secureApiHeaders) try { - const { sessionToken } = cookieManager.getCookie(headers) + const { sessionToken } = ctx.cookies.manager.getCookie(headers) if (!sessionToken) return { session: null, headers: newHeaders } - const claims = await jwtManager.verifyToken(sessionToken) + const claims = await jose.manager.verifyToken(sessionToken) const parsedClaims = identity.skipValidation ? claims : await identity.schemaRegistry.parseWithJWT(claims) const { exp, iat: _iat, mexp: _mexp, ...defaultPayload } = parsedClaims const userClaims = identity.skipValidation ? defaultPayload : await identity.schemaRegistry.parse(defaultPayload) @@ -50,7 +49,7 @@ export const getSession = ({ } const expiresAt = calc.expiresAt const issuedAt = strategy === "absolute" ? parsedClaims.iat : Math.floor(Date.now() / 1000) - const newSessionToken = await jwtManager.createToken({ + const newSessionToken = await jose.manager.createToken({ ...userClaims, exp: Math.floor(expiresAt.getTime() / 1000), iat: issuedAt, @@ -62,7 +61,7 @@ export const getSession = ({ user: userClaims, expires: expiresAt.toISOString(), } as unknown as Session, - headers: cookieManager.setCookie({ sessionToken: newSessionToken }), + headers: ctx.cookies.manager.setCookie({ sessionToken: newSessionToken }), } } catch (error) { logger?.log("AUTH_SESSION_INVALID", { structuredData: { error_type: getErrorName(error) } }) diff --git a/packages/core/src/session/stateless/index.ts b/packages/core/src/session/stateless/index.ts index c5c5a142..b641d022 100644 --- a/packages/core/src/session/stateless/index.ts +++ b/packages/core/src/session/stateless/index.ts @@ -24,10 +24,10 @@ import { verifyMagicLink, } from "@/session/stateless/unsupported.ts" import type { User } from "@/@types/index.ts" -import type { SessionStrategy, InternalStatelessContext } from "@/@types/internal.ts" +import type { SessionStrategy, InternalContextForStateless } from "@/@types/internal.ts" export const createStatelessStrategy = ( - ctx: InternalStatelessContext + ctx: InternalContextForStateless ): SessionStrategy => { return { signIn: signIn(ctx), diff --git a/packages/core/src/session/stateless/is-provider-connected.ts b/packages/core/src/session/stateless/is-provider-connected.ts index 1c97064a..d5db95ae 100644 --- a/packages/core/src/session/stateless/is-provider-connected.ts +++ b/packages/core/src/session/stateless/is-provider-connected.ts @@ -1,12 +1,12 @@ import { getCookie } from "@/shared/http/cookie.ts" import { getErrorName } from "@/errors/aura-error.ts" -import type { InternalStatelessContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateless, SessionStrategy } from "@/@types/internal.ts" -export const isProviderConnected = ({ ctx, cookies }: InternalStatelessContext): SessionStrategy["isProviderConnected"] => { - const { logger, jwtManager: jwt } = ctx +export const isProviderConnected = (ctx: InternalContextForStateless): SessionStrategy["isProviderConnected"] => { + const { logger, jose } = ctx return async ({ oauthId, headers }) => { - const cookieName = `${cookies().accessToken.name}.${oauthId}` + const cookieName = `${ctx.cookies.store.accessToken.name}.${oauthId}` let cookieValue: string try { cookieValue = getCookie(headers, cookieName) @@ -18,7 +18,7 @@ export const isProviderConnected = ({ ctx, cookies }: InternalStatelessContext): } try { - const decodedToken = await jwt.verifyToken(cookieValue) + const decodedToken = await jose.manager.verifyToken(cookieValue) return !!decodedToken } catch (error) { logger?.log("AUTH_SESSION_INVALID", { structuredData: { error_type: getErrorName(error) } }) diff --git a/packages/core/src/session/stateless/oauth-callback.ts b/packages/core/src/session/stateless/oauth-callback.ts index 582ff5c0..cf8633ac 100644 --- a/packages/core/src/session/stateless/oauth-callback.ts +++ b/packages/core/src/session/stateless/oauth-callback.ts @@ -9,9 +9,9 @@ import { createAccessToken, getUserInfo } from "@/shared/oauth2/index.ts" import { getCookie, getExpiredCookie, getOptionalCookie } from "@/shared/http/cookie.ts" import { isOIDCProvider, resolveOpenIDProvider } from "@/shared/oidc/resolve-provider.ts" import { getOriginURL, isRelativeURL, isSameOrigin, isTrustedOrigin } from "@/shared/utils/get-origin-url.ts" -import type { InternalStatelessContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateless, SessionStrategy } from "@/@types/internal.ts" -export const oauthCallback = ({ ctx }: InternalStatelessContext): SessionStrategy["oauthCallback"] => { +export const oauthCallback = (ctx: InternalContextForStateless): SessionStrategy["oauthCallback"] => { return async ({ oauthId, request, params: { code, state } }) => { const { oauth: providers, cookies, jose, logger, trustedOrigins } = ctx @@ -21,18 +21,18 @@ export const oauthCallback = ({ ctx }: InternalStatelessContext): SessionStrateg } const isOIDC = isOIDCProvider(oauthConfig) - const cookieState = getCookie(request, cookies.state.name) - const codeVerifier = getCookie(request, cookies.codeVerifier.name) - const cookieNonce = isOIDC ? getOptionalCookie(request, cookies.nonce.name) : undefined - const cookieRedirectTo = getCookie(request, cookies.redirectTo.name) - const cookieRedirectURI = getCookie(request, cookies.redirectURI.name) + const cookieState = getCookie(request, cookies.store.state.name) + const codeVerifier = getCookie(request, cookies.store.codeVerifier.name) + const cookieNonce = isOIDC ? getOptionalCookie(request, cookies.store.nonce.name) : undefined + const cookieRedirectTo = getCookie(request, cookies.store.redirectTo.name) + const cookieRedirectURI = getCookie(request, cookies.store.redirectURI.name) const clearCookieHeaders = new HeadersBuilder(cacheControl) - .setCookie(cookies.state.name, "", getExpiredCookie(cookies.state.attributes)) - .setCookie(cookies.redirectURI.name, "", getExpiredCookie(cookies.redirectURI.attributes)) - .setCookie(cookies.redirectTo.name, "", getExpiredCookie(cookies.redirectTo.attributes)) - .setCookie(cookies.codeVerifier.name, "", getExpiredCookie(cookies.codeVerifier.attributes)) - .setCookie(cookies.nonce.name, "", getExpiredCookie(cookies.nonce.attributes)) + .setCookie(cookies.store.state.name, "", getExpiredCookie(cookies.store.state.attributes)) + .setCookie(cookies.store.redirectURI.name, "", getExpiredCookie(cookies.store.redirectURI.attributes)) + .setCookie(cookies.store.redirectTo.name, "", getExpiredCookie(cookies.store.redirectTo.attributes)) + .setCookie(cookies.store.codeVerifier.name, "", getExpiredCookie(cookies.store.codeVerifier.attributes)) + .setCookie(cookies.store.nonce.name, "", getExpiredCookie(cookies.store.nonce.attributes)) if (!timingSafeEqual(cookieState, state)) { logger?.log("MISMATCHING_STATE", { @@ -95,10 +95,10 @@ export const oauthCallback = ({ ctx }: InternalStatelessContext): SessionStrateg } const userInfo = await getUserInfo(resolvedConfig, accessToken, logger) - const session = await ctx.sessionStrategy.createSession({ session: userInfo, request }) - const csrfToken = await createCSRF(jose) + const session = await ctx.session.strategy.createSession({ session: userInfo, request }) + const csrfToken = await createCSRF(jose.instance) const tokenPayload = transformToTokenPayload(accessToken) - const providerToken = await ctx.jwtManager.createToken(tokenPayload) + const providerToken = await jose.manager.createToken(tokenPayload) logger?.log("OAUTH_CALLBACK_SUCCESS", { structuredData: { @@ -108,9 +108,9 @@ export const oauthCallback = ({ ctx }: InternalStatelessContext): SessionStrateg const headers = clearCookieHeaders .setHeader("Location", cookieRedirectTo) - .setCookie(cookies.sessionToken.name, session, cookies.sessionToken.attributes) - .setCookie(cookies.csrfToken.name, csrfToken, cookies.csrfToken.attributes) - .setCookie(`${cookies.accessToken.name}.${oauthId}`, providerToken, cookies.accessToken.attributes) + .setCookie(cookies.store.sessionToken.name, session, cookies.store.sessionToken.attributes) + .setCookie(cookies.store.csrfToken.name, csrfToken, cookies.store.csrfToken.attributes) + .setCookie(`${cookies.store.accessToken.name}.${oauthId}`, providerToken, cookies.store.accessToken.attributes) .toHeaders() return Response.json({ oauth: oauthId }, { status: 302, headers: headers }) diff --git a/packages/core/src/session/stateless/refresh-session.ts b/packages/core/src/session/stateless/refresh-session.ts index 47bb1339..568d7438 100644 --- a/packages/core/src/session/stateless/refresh-session.ts +++ b/packages/core/src/session/stateless/refresh-session.ts @@ -1,24 +1,23 @@ import { getErrorName } from "@/errors/aura-error.ts" import { updateExpires } from "@/shared/utils/session-strategy.ts" import type { Session, User } from "@/@types/index.ts" -import type { InternalStatelessContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateless, SessionStrategy } from "@/@types/internal.ts" -export const refreshSession = ({ - ctx, - cookieManager, -}: InternalStatelessContext): SessionStrategy["refreshSession"] => { - const { logger, jwtManager: jwt, identity, sessionConfig } = ctx +export const refreshSession = ( + ctx: InternalContextForStateless +): SessionStrategy["refreshSession"] => { + const { logger, jose, identity, session } = ctx - const maxAge = sessionConfig?.maxAge ?? sessionConfig?.jwt?.maxAge ?? 60 * 60 * 24 * 15 - const strategy = sessionConfig?.expirationStrategy ?? sessionConfig?.jwt?.expirationStrategy ?? "absolute" + const maxAge = session?.config?.maxAge ?? session?.config?.jwt?.maxAge ?? 60 * 60 * 24 * 15 + const strategy = session?.config?.expirationStrategy ?? session?.config?.jwt?.expirationStrategy ?? "absolute" return async ({ session, headers }) => { try { - const { sessionToken } = cookieManager.getCookie(headers) + const { sessionToken } = ctx.cookies.manager.getCookie(headers) if (!sessionToken) { - return { session: null, headers: cookieManager.clear() } + return { session: null, headers: ctx.cookies.manager.clear() } } - const claims = await jwt.verifyToken(sessionToken) + const claims = await jose.manager.verifyToken(sessionToken) const parsedClaims = identity.skipValidation ? claims : await identity.schemaRegistry.parseWithJWT(claims) const { exp, mexp, iat, ...claimsWithoutJWTFields } = parsedClaims @@ -43,17 +42,17 @@ export const refreshSession = ({ } const verifiedPayload = await identity.schemaRegistry.parse(updatedSession.user) const issuedAt = strategy === "absolute" ? iat : Math.floor(Date.now() / 1000) - const newToken = await jwt.createToken({ + const newToken = await jose.manager.createToken({ ...verifiedPayload, exp: Math.floor(expiresAt.getTime() / 1000), iat: issuedAt, mexp, }) updatedSession.expires = new Date(updatedSession.expires).toISOString() - return { session: updatedSession, headers: cookieManager.setCookie({ sessionToken: newToken }) } + return { session: updatedSession, headers: ctx.cookies.manager.setCookie({ sessionToken: newToken }) } } catch (error) { logger?.log("AUTH_SESSION_INVALID", { structuredData: { error_type: getErrorName(error) } }) - return { session: null, headers: cookieManager.clear() } + return { session: null, headers: ctx.cookies.manager.clear() } } } } diff --git a/packages/core/src/session/stateless/refresh-userInfo.ts b/packages/core/src/session/stateless/refresh-userInfo.ts index b46fa3e5..9e858dce 100644 --- a/packages/core/src/session/stateless/refresh-userInfo.ts +++ b/packages/core/src/session/stateless/refresh-userInfo.ts @@ -1,13 +1,11 @@ -import { refreshSession as __refreshSession } from "@/session/stateless/refresh-session.ts" +import { refreshSession as refreshSessionHelper } from "@/session/stateless/refresh-session.ts" import type { User } from "@/@types/index.ts" -import type { InternalStatelessContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateless, SessionStrategy } from "@/@types/internal.ts" -export const refreshUserInfo = ({ - ctx, - cookies, - cookieManager, -}: InternalStatelessContext): SessionStrategy["refreshUserInfo"] => { - const refreshSession = __refreshSession({ ctx, cookies, cookieManager }) +export const refreshUserInfo = ( + ctx: InternalContextForStateless +): SessionStrategy["refreshUserInfo"] => { + const refreshSession = refreshSessionHelper(ctx) return async ({ headers, user }) => { const value = await refreshSession({ headers, session: { user } }) return value diff --git a/packages/core/src/session/stateless/revoke-token.ts b/packages/core/src/session/stateless/revoke-token.ts index ea2cc3e6..784b32d9 100644 --- a/packages/core/src/session/stateless/revoke-token.ts +++ b/packages/core/src/session/stateless/revoke-token.ts @@ -3,17 +3,17 @@ import { toUnionHeaders } from "@/shared/http/headers.ts" import { secureApiHeaders } from "@/shared/http/headers.ts" import { getCookie, getExpiredCookie } from "@/shared/http/cookie.ts" import { revokeProviderToken } from "@/shared/utils/revoke-token.ts" -import type { InternalStatelessContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateless, SessionStrategy } from "@/@types/internal.ts" -export const revokeToken = ({ ctx, cookies }: InternalStatelessContext): SessionStrategy["revokeToken"] => { - const { logger, identity, jwtManager: jwt } = ctx +export const revokeToken = (ctx: InternalContextForStateless): SessionStrategy["revokeToken"] => { + const { logger, identity, jose } = ctx return async ({ oauthId, provider, disconnect, headers }) => { - const cookieName = `${cookies().accessToken.name}.${oauthId}` + const cookieName = `${ctx.cookies.store.accessToken.name}.${oauthId}` const cookie = getCookie(headers, cookieName) if (!disconnect) { - const decodedToken = await jwt.verifyToken(cookie) + const decodedToken = await jose.manager.verifyToken(cookie) const tokens = await identity.schemaRegistry.parseOAuthTokens(decodedToken) logger?.log("OAUTH_ACCESS_TOKEN_REQUEST_INITIATED", { @@ -27,7 +27,7 @@ export const revokeToken = ({ ctx, cookies }: InternalStatelessContext): Session }) } const builder = new HeadersBuilder(secureApiHeaders) - .setCookie(cookieName, "", getExpiredCookie(cookies().accessToken.attributes)) + .setCookie(cookieName, "", getExpiredCookie(ctx.cookies.store.accessToken.attributes)) .toHeaders() return toUnionHeaders(builder, headers) } diff --git a/packages/core/src/session/stateless/sign-in-credentials.ts b/packages/core/src/session/stateless/sign-in-credentials.ts index 3405b793..77c296ea 100644 --- a/packages/core/src/session/stateless/sign-in-credentials.ts +++ b/packages/core/src/session/stateless/sign-in-credentials.ts @@ -1,11 +1,11 @@ -import { createSession as __createSession } from "@/session/stateless/create-session.ts" +import { createSession as createSessionHelper } from "@/session/stateless/create-session.ts" import type { User } from "@/@types/index.ts" -import type { InternalStatelessContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateless, SessionStrategy } from "@/@types/internal.ts" export const signInCredentials = ( - ctx: InternalStatelessContext + ctx: InternalContextForStateless ): SessionStrategy["signInCredentials"] => { - const createSession = __createSession(ctx) + const createSession = createSessionHelper(ctx) return async ({ payload, request }): Promise => { return await createSession({ request, session: payload as any }) diff --git a/packages/core/src/session/stateless/sign-in.ts b/packages/core/src/session/stateless/sign-in.ts index 5b7257d7..53285435 100644 --- a/packages/core/src/session/stateless/sign-in.ts +++ b/packages/core/src/session/stateless/sign-in.ts @@ -3,9 +3,9 @@ import { cacheControl } from "@/shared/http/headers.ts" import { createOIDCAuthorizationURL } from "@/shared/oidc/authorization-url.ts" import { isOIDCProvider, resolveOpenIDProvider } from "@/shared/oidc/resolve-provider.ts" import { createAuthorizationURL, createRedirectTo, createRedirectURI } from "@/shared/oauth2/authorization.ts" -import type { InternalStatelessContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateless, SessionStrategy } from "@/@types/internal.ts" -export const signIn = ({ ctx, cookies }: InternalStatelessContext): SessionStrategy["signIn"] => { +export const signIn = (ctx: InternalContextForStateless): SessionStrategy["signIn"] => { const { logger } = ctx return async ({ oauthId, provider, request, redirectTo }) => { @@ -49,13 +49,13 @@ export const signIn = ({ ctx, cookies }: InternalStatelessContext): SessionStrat const headersBuilder = new HeadersBuilder(cacheControl) .setHeader("Location", authorization) - .setCookie(cookies().state.name, state, cookies().state.attributes) - .setCookie(cookies().redirectURI.name, redirectURI, cookies().redirectURI.attributes) - .setCookie(cookies().redirectTo.name, redirectToValue, cookies().redirectTo.attributes) - .setCookie(cookies().codeVerifier.name, codeVerifier, cookies().codeVerifier.attributes) + .setCookie(ctx.cookies.store.state.name, state, ctx.cookies.store.state.attributes) + .setCookie(ctx.cookies.store.redirectURI.name, redirectURI, ctx.cookies.store.redirectURI.attributes) + .setCookie(ctx.cookies.store.redirectTo.name, redirectToValue, ctx.cookies.store.redirectTo.attributes) + .setCookie(ctx.cookies.store.codeVerifier.name, codeVerifier, ctx.cookies.store.codeVerifier.attributes) if (nonce) { - headersBuilder.setCookie(cookies().nonce.name, nonce, cookies().nonce.attributes) + headersBuilder.setCookie(ctx.cookies.store.nonce.name, nonce, ctx.cookies.store.nonce.attributes) } return { success: true, diff --git a/packages/core/src/session/stateless/sign-up.ts b/packages/core/src/session/stateless/sign-up.ts index 3276ff2a..69c4f3bb 100644 --- a/packages/core/src/session/stateless/sign-up.ts +++ b/packages/core/src/session/stateless/sign-up.ts @@ -1,11 +1,11 @@ -import { createSession as __createSession } from "@/session/stateless/create-session.ts" +import { createSession as createSessionHelper } from "@/session/stateless/create-session.ts" import type { User } from "@/@types/index.ts" -import type { InternalStatelessContext, SessionStrategy } from "@/@types/internal.ts" +import type { InternalContextForStateless, SessionStrategy } from "@/@types/internal.ts" export const signUp = ( - ctx: InternalStatelessContext + ctx: InternalContextForStateless ): SessionStrategy["signUp"] => { - const createSession = __createSession(ctx) + const createSession = createSessionHelper(ctx) return async ({ payload, request }) => { return await createSession({ request, session: payload as any }) diff --git a/packages/core/src/session/stateless/unsupported.ts b/packages/core/src/session/stateless/unsupported.ts index 9269aa61..54d13c09 100644 --- a/packages/core/src/session/stateless/unsupported.ts +++ b/packages/core/src/session/stateless/unsupported.ts @@ -1,7 +1,7 @@ import { AuraAuthError } from "@/errors/aura-error.ts" -import type { SessionStrategy, InternalStatelessContext } from "@/@types/internal.ts" +import type { SessionStrategy, InternalContextForStateless } from "@/@types/internal.ts" -export const revokeSession = (_: InternalStatelessContext): SessionStrategy["revokeSession"] => { +export const revokeSession = (_: InternalContextForStateless): SessionStrategy["revokeSession"] => { return async () => { throw new AuraAuthError({ code: "UNSUPPORTED_FUNCTION_IN_STATELESS_MODE", @@ -10,7 +10,7 @@ export const revokeSession = (_: InternalStatelessContext): SessionStrategy["rev } } -export const sendVerificationEmail = (_: InternalStatelessContext): SessionStrategy["sendVerificationEmail"] => { +export const sendVerificationEmail = (_: InternalContextForStateless): SessionStrategy["sendVerificationEmail"] => { return async () => { throw new AuraAuthError({ code: "UNSUPPORTED_FUNCTION_IN_STATELESS_MODE", @@ -19,7 +19,7 @@ export const sendVerificationEmail = (_: InternalStatelessContext): SessionStrat } } -export const verifyEmail = (_: InternalStatelessContext): SessionStrategy["verifyEmail"] => { +export const verifyEmail = (_: InternalContextForStateless): SessionStrategy["verifyEmail"] => { return async () => { throw new AuraAuthError({ code: "UNSUPPORTED_FUNCTION_IN_STATELESS_MODE", @@ -28,7 +28,7 @@ export const verifyEmail = (_: InternalStatelessContext): SessionStrategy["verif } } -export const forgotPassword = (_: InternalStatelessContext): SessionStrategy["forgotPassword"] => { +export const forgotPassword = (_: InternalContextForStateless): SessionStrategy["forgotPassword"] => { return async () => { throw new AuraAuthError({ code: "UNSUPPORTED_FUNCTION_IN_STATELESS_MODE", @@ -37,7 +37,7 @@ export const forgotPassword = (_: InternalStatelessContext): SessionStrategy["fo } } -export const resetPassword = (_: InternalStatelessContext): SessionStrategy["resetPassword"] => { +export const resetPassword = (_: InternalContextForStateless): SessionStrategy["resetPassword"] => { return async () => { throw new AuraAuthError({ code: "UNSUPPORTED_FUNCTION_IN_STATELESS_MODE", @@ -46,7 +46,7 @@ export const resetPassword = (_: InternalStatelessContext): SessionStrategy["res } } -export const changePassword = (_: InternalStatelessContext): SessionStrategy["changePassword"] => { +export const changePassword = (_: InternalContextForStateless): SessionStrategy["changePassword"] => { return async () => { throw new AuraAuthError({ code: "UNSUPPORTED_FUNCTION_IN_STATELESS_MODE", @@ -55,7 +55,7 @@ export const changePassword = (_: InternalStatelessContext): SessionStrategy["ch } } -export const changeEmail = (_: InternalStatelessContext): SessionStrategy["changeEmail"] => { +export const changeEmail = (_: InternalContextForStateless): SessionStrategy["changeEmail"] => { return async () => { throw new AuraAuthError({ code: "UNSUPPORTED_FUNCTION_IN_STATELESS_MODE", @@ -64,7 +64,7 @@ export const changeEmail = (_: InternalStatelessContext): SessionStrategy["chang } } -export const verifyEmailChange = (_: InternalStatelessContext): SessionStrategy["verifyEmailChange"] => { +export const verifyEmailChange = (_: InternalContextForStateless): SessionStrategy["verifyEmailChange"] => { return async () => { throw new AuraAuthError({ code: "UNSUPPORTED_FUNCTION_IN_STATELESS_MODE", @@ -73,7 +73,7 @@ export const verifyEmailChange = (_: InternalStatelessContext): SessionStrategy[ } } -export const cancelEmailChange = (_: InternalStatelessContext): SessionStrategy["cancelEmailChange"] => { +export const cancelEmailChange = (_: InternalContextForStateless): SessionStrategy["cancelEmailChange"] => { return async () => { throw new AuraAuthError({ code: "UNSUPPORTED_FUNCTION_IN_STATELESS_MODE", @@ -82,7 +82,7 @@ export const cancelEmailChange = (_: InternalStatelessContext): SessionStrategy[ } } -export const sendMagicLink = (_: InternalStatelessContext): SessionStrategy["sendMagicLink"] => { +export const sendMagicLink = (_: InternalContextForStateless): SessionStrategy["sendMagicLink"] => { return async () => { throw new AuraAuthError({ code: "UNSUPPORTED_FUNCTION_IN_STATELESS_MODE", @@ -91,7 +91,7 @@ export const sendMagicLink = (_: InternalStatelessContext): SessionStrategy["sen } } -export const verifyMagicLink = (_: InternalStatelessContext): SessionStrategy["verifyMagicLink"] => { +export const verifyMagicLink = (_: InternalContextForStateless): SessionStrategy["verifyMagicLink"] => { return async () => { throw new AuraAuthError({ code: "UNSUPPORTED_FUNCTION_IN_STATELESS_MODE", diff --git a/packages/core/src/session/strategy.ts b/packages/core/src/session/strategy.ts index 7f9f2994..8e5d9897 100644 --- a/packages/core/src/session/strategy.ts +++ b/packages/core/src/session/strategy.ts @@ -1,32 +1,29 @@ import { AuraAuthError } from "@/errors/aura-error.ts" import { isStatelessStrategy } from "@/shared/assert.ts" -import { createCookieManager } from "@/session/cookie-manager.ts" import { createStatefulStrategy } from "@/session/stateful/index.ts" import { createStatelessStrategy } from "@/session/stateless/index.ts" import type { User, FromShapeToObject, Identities } from "@/@types/index.ts" import type { SessionStrategy, - CreateSessionStrategyOptions, - InternalStatefulContext, - InternalStatelessContext, + InternalContextForStateless, + InternalContextForStateful, + InternalContext, } from "@/@types/internal.ts" export const createSessionStrategy = ( - config: CreateSessionStrategyOptions + ctx: InternalContext ): SessionStrategy & User> => { - const strategy = config?.ctx?.sessionConfig?.strategy ?? "jwt" - const cookieManager = createCookieManager(config.cookies) - const ctx = { ...config, cookieManager } + const strategy = ctx.session?.config?.strategy ?? "jwt" - if (!isStatelessStrategy(config?.ctx?.sessionConfig) && !config?.ctx?.sessionConfig?.adapter) { + if (!isStatelessStrategy(ctx.session?.config) && !ctx.session?.config?.adapter) { throw new AuraAuthError({ code: "MISSING_ADAPTER_IN_STATEFUL_STRATEGY" }) } switch (strategy) { case "jwt": - return createStatelessStrategy(ctx as InternalStatelessContext) + return createStatelessStrategy(ctx as InternalContextForStateless) case "database": - return createStatefulStrategy(ctx as InternalStatefulContext) + return createStatefulStrategy(ctx as InternalContextForStateful) default: throw new AuraAuthError({ code: "INVALID_SESSION_STRATEGY" }) } diff --git a/packages/core/src/shared/crypto.ts b/packages/core/src/shared/crypto.ts index 2fbd901b..8779f523 100644 --- a/packages/core/src/shared/crypto.ts +++ b/packages/core/src/shared/crypto.ts @@ -38,7 +38,7 @@ export const createPKCE = async (verifier?: string) => { return { codeVerifier, codeChallenge, method: "S256" } } -export const assertCSRFTokenCookie = async (jose: RouterGlobalContext["jose"], cookie?: string) => { +export const assertCSRFTokenCookie = async (jose: RouterGlobalContext["jose"]["instance"], cookie?: string) => { try { if (cookie) { await jose.verifyJWS(cookie) @@ -54,7 +54,7 @@ export const assertCSRFTokenCookie = async (jose: RouterGlobalContext["jose"], c * @param csrfCookie - Optional existing CSRF cookie to verify and reuse * @returns Signed CSRF token */ -export const createCSRF = async (jose: RouterGlobalContext["jose"], csrfCookie?: string) => { +export const createCSRF = async (jose: RouterGlobalContext["jose"]["instance"], csrfCookie?: string) => { try { if (csrfCookie) { await jose.verifyJWS(csrfCookie) diff --git a/packages/core/src/shared/utils/api.ts b/packages/core/src/shared/utils/api.ts index b0fe9e61..11811192 100644 --- a/packages/core/src/shared/utils/api.ts +++ b/packages/core/src/shared/utils/api.ts @@ -45,20 +45,20 @@ export const createValidation = (ctx: RouterGlobalContext, headersInit?: Headers }, verifySession: () => { steps.push(async () => { - if (isStatelessStrategy(ctx.sessionConfig)) { + if (isStatelessStrategy(ctx.session.config)) { await verifySessionToken({ headers: output.headers, - cookies: ctx.cookies, - jwt: ctx.jwtManager, + cookies: ctx.cookies.store, + jwt: ctx.jose.manager, logger: ctx.logger, }) } else { - const { sessionToken } = createCookieManager(() => ctx.cookies).getCookie(new Headers(headersInit)) + const { sessionToken } = createCookieManager(() => ctx.cookies.store).getCookie(new Headers(headersInit)) if (!sessionToken) { throw new AuraAuthError({ code: "SESSION_NOT_FOUND" }) } const tokenHash = await createHash(sessionToken) - const session = await ctx.sessionConfig.adapter.getSessionByToken(tokenHash) + const session = await ctx.session.config.adapter.getSessionByToken(tokenHash) if (!session || session.status !== "active" || new Date() > session.expiresAt) { throw new AuraAuthError({ code: "SESSION_NOT_FOUND" }) } @@ -72,8 +72,10 @@ export const createValidation = (ctx: RouterGlobalContext, headersInit?: Headers }, verifyCSRFToken: (skipCSRFCheck: boolean, doubleSubmitToken: string | undefined, generateNewToken: boolean = false) => { steps.push(async () => { - const existingCSRFToken = getOptionalCookie(output.headers, ctx.cookies.csrfToken.name) - const csrfTokenValue = generateNewToken ? await createCSRF(ctx.jose, existingCSRFToken) : existingCSRFToken + const existingCSRFToken = getOptionalCookie(output.headers, ctx.cookies.store.csrfToken.name) + const csrfTokenValue = generateNewToken + ? await createCSRF(ctx.jose.instance, existingCSRFToken) + : existingCSRFToken await internal_verifyCSRFToken({ ctx, headers: output.headers, @@ -183,7 +185,7 @@ export const internal_verifyCSRFToken = async ({ let csrfToken = csrfTokenValue || null try { - csrfToken = csrfToken || getCookie(headers, ctx.cookies.csrfToken.name) + csrfToken = csrfToken || getCookie(headers, ctx.cookies.store.csrfToken.name) } catch (cause) { ctx.logger?.log("CSRF_TOKEN_MISSING") @@ -202,7 +204,7 @@ export const internal_verifyCSRFToken = async ({ } try { - await ctx.jose.verifyJWS(csrfToken) + await ctx.jose.instance.verifyJWS(csrfToken) } catch (cause) { ctx.logger?.log("CSRF_TOKEN_INVALID", { structuredData: { @@ -231,7 +233,7 @@ export const internal_verifyCSRFToken = async ({ } try { - await verifyCSRF(ctx.jose, csrfToken, requestToken) + await verifyCSRF(ctx.jose.instance, csrfToken, requestToken) } catch (cause) { ctx.logger?.log("CSRF_TOKEN_INVALID", { structuredData: { diff --git a/packages/core/src/shared/utils/session-strategy.ts b/packages/core/src/shared/utils/session-strategy.ts index 3a4e86a9..750c13b1 100644 --- a/packages/core/src/shared/utils/session-strategy.ts +++ b/packages/core/src/shared/utils/session-strategy.ts @@ -1,7 +1,7 @@ import { isNullOrUndefined } from "@/shared/assert.ts" import { createFingerprint, getDeviceInfo } from "@/shared/http/request-metadata.ts" import type { ExpirationStrategy } from "@/@types/index.ts" -import type { InternalExpirationResult, InternalStatefulContext } from "@/@types/internal.ts" +import type { InternalContextForStateful, InternalExpirationResult } from "@/@types/internal.ts" /** Default sliding threshold: extend when less than 25% of maxAge remains. */ export const DEFAULT_SLIDING_THRESHOLD_RATIO = 0.25 @@ -9,17 +9,17 @@ export const DEFAULT_SLIDING_THRESHOLD_RATIO = 0.25 /** Default touch threshold: write lastActivityAt at most once per 5 minutes. */ export const DEFAULT_TOUCH_THRESHOLD_MS = 5 * 60 * 1000 -export const createDevice = ({ ctx: { sessionConfig } }: InternalStatefulContext) => { +export const createDevice = ({ session }: InternalContextForStateful) => { return async (userId: string, request: Request) => { const { userAgent, browser, platform, deviceType, ip, name } = getDeviceInfo(request) const fingerprint = await createFingerprint(request) - const device = await sessionConfig.adapter.getDeviceByFingerprint(userId, fingerprint) + const device = await session.config.adapter.getDeviceByFingerprint(userId, fingerprint) if (device) { - await sessionConfig.adapter.updateDevice(device.id, { lastSeenAt: new Date() }) + await session.config.adapter.updateDevice(device.id, { lastSeenAt: new Date() }) return device } - return await sessionConfig.adapter.createDevice({ + return await session.config.adapter.createDevice({ userId, userAgent, browser, diff --git a/packages/core/src/shared/utils/session.ts b/packages/core/src/shared/utils/session.ts index 4bb4f1a8..872ebbf6 100644 --- a/packages/core/src/shared/utils/session.ts +++ b/packages/core/src/shared/utils/session.ts @@ -1,6 +1,6 @@ import { getCookie } from "@/shared/http/cookie.ts" import { AuraAuthError, getErrorName } from "@/errors/aura-error.ts" -import type { InternalCookieStoreConfig, InternalLogger, JWTManager, SchemaRegistryContext } from "@/@types/internal.ts" +import type { InternalCookieStoreConfig, InternalLogger, JoseManager, SchemaRegistryContext } from "@/@types/internal.ts" export const getStandardSession = async ({ sessionToken, @@ -8,7 +8,7 @@ export const getStandardSession = async ({ identity, }: { sessionToken: string - jwt: JWTManager + jwt: JoseManager identity: SchemaRegistryContext }) => { const claims = await jwt.verifyToken(sessionToken) @@ -29,7 +29,7 @@ export const verifySessionToken = async ({ logger, }: { headers: Headers - jwt: JWTManager + jwt: JoseManager cookies: InternalCookieStoreConfig logger: InternalLogger | undefined }) => {