@peopleplus/auth
Advanced tools
Comparing version
import { type Handle, type RequestEvent } from '@sveltejs/kit'; | ||
import { type User, type Session } from 'lucia'; | ||
import { TimeSpan, type User, type Session } from 'lucia'; | ||
import { type DatabaseAdapter } from './database.js'; | ||
import { type Auth0ProviderOptions } from './auth0.js'; | ||
import { Auth0, type Auth0ProviderOptions } from './auth0.js'; | ||
import type { DatabaseSessionAttributesWithProvided, DatabaseUserAttributesWithProvided, ProvidedDatabaseUserAttributes } from './index.js'; | ||
import { MicrosoftEntraId, type MicrosoftEntraIdProviderOptions } from './entra.js'; | ||
export type OAuthProviderOptions = { | ||
type: 'auth0'; | ||
options: Auth0ProviderOptions; | ||
} | { | ||
type: 'entra'; | ||
options: MicrosoftEntraIdProviderOptions; | ||
}; | ||
export type OAuthProvider = { | ||
type: 'auth0'; | ||
provider: Auth0; | ||
} | { | ||
type: 'entra'; | ||
provider: MicrosoftEntraId; | ||
}; | ||
export type AuthOptions<ExposedSessionAttributes extends Record<string, unknown> = Record<never, never>, ExposedUserAttributes extends Record<string, unknown> = Record<never, never>> = { | ||
auth0: Auth0ProviderOptions; | ||
sessionLifetime?: TimeSpan; | ||
provider: OAuthProviderOptions; | ||
adapter: DatabaseAdapter; | ||
@@ -60,3 +76,3 @@ dev?: boolean; | ||
private lucia; | ||
private getAuth0Provider; | ||
private getOAuthProvider; | ||
private afterLoginURL; | ||
@@ -88,6 +104,6 @@ private afterLogoutURL; | ||
handlers(): { | ||
handleSignInRedirect: (event: Pick<RequestEvent<Partial<Record<string, string>>, string | null>, "url" | "cookies" | "request">, opts?: { | ||
handleSignInRedirect: (event: Pick<RequestEvent, "url" | "cookies" | "request">, opts?: { | ||
additionalAuthParams: Record<string, string>; | ||
} | undefined) => Promise<Response>; | ||
handleSignOut: (event: Pick<RequestEvent<Partial<Record<string, string>>, string | null>, "url" | "cookies" | "request"> & { | ||
}) => Promise<Response>; | ||
handleSignOut: (event: Pick<RequestEvent, "url" | "cookies" | "request"> & { | ||
locals: { | ||
@@ -97,7 +113,4 @@ session: Session | null; | ||
}) => Promise<import("@sveltejs/kit").ActionFailure<undefined>>; | ||
handleAuthCallback: (event: RequestEvent<Partial<Record<string, string>>, string | null>) => Promise<Response>; | ||
hook: ({ event, resolve }: { | ||
event: RequestEvent<Partial<Record<string, string>>, string | null>; | ||
resolve(event: RequestEvent<Partial<Record<string, string>>, string | null>, opts?: import("@sveltejs/kit").ResolveOptions | undefined): Response | Promise<Response>; | ||
}) => Promise<Response>; | ||
handleAuthCallback: (event: RequestEvent) => Promise<Response>; | ||
hook: ({ event, resolve }: Parameters<Handle>[0]) => Promise<Response>; | ||
}; | ||
@@ -200,8 +213,8 @@ /** | ||
path: string; | ||
secure?: boolean | undefined; | ||
domain?: string | undefined; | ||
sameSite?: "lax" | "strict" | "none" | undefined; | ||
httpOnly?: boolean | undefined; | ||
maxAge?: number | undefined; | ||
expires?: Date | undefined; | ||
secure?: boolean; | ||
domain?: string; | ||
sameSite?: "lax" | "strict" | "none"; | ||
httpOnly?: boolean; | ||
maxAge?: number; | ||
expires?: Date; | ||
}; | ||
@@ -208,0 +221,0 @@ }; |
107
dist/auth.js
import { fail, redirect, error } from '@sveltejs/kit'; | ||
import { OAuth2RequestError, generateState } from 'arctic'; | ||
import { OAuth2RequestError, OAuth2Tokens, generateCodeVerifier, generateState } from 'arctic'; | ||
import { Lucia, TimeSpan, generateId } from 'lucia'; | ||
@@ -9,2 +9,3 @@ import { parseJWT } from 'oslo/jwt'; | ||
import { trace } from '@opentelemetry/api'; | ||
import { MicrosoftEntraId } from './entra.js'; | ||
export class PeoplePlusAuth { | ||
@@ -16,3 +17,4 @@ static DEFAULT_AUTH_CALLBACK_URL = '/auth/callback'; | ||
lucia; | ||
getAuth0Provider; | ||
// private getAuth0Provider: (event: Pick<RequestEvent, 'url'>) => Auth0; | ||
getOAuthProvider; | ||
afterLoginURL; | ||
@@ -29,3 +31,3 @@ afterLogoutURL; | ||
this.adapter = instrumentDatabaseAdapter(opts.adapter); | ||
this.getAuth0Provider = auth0ProviderGetter(opts.auth0); | ||
this.getOAuthProvider = oauthProviderGetter(opts.provider); | ||
this.afterLoginURL = opts.afterLoginURL ?? PeoplePlusAuth.DEFAULT_AFTER_LOGIN_URL; | ||
@@ -40,3 +42,3 @@ this.afterLogoutURL = opts.afterLoginURL ?? PeoplePlusAuth.DEFAULT_AFTER_LOGOUT_URL; | ||
this.lucia = new Lucia(this.adapter, { | ||
sessionExpiresIn: new TimeSpan(1, 'd'), | ||
sessionExpiresIn: opts.sessionLifetime ?? new TimeSpan(1, 'd'), | ||
sessionCookie: { attributes: { secure: !opts.dev } }, | ||
@@ -97,4 +99,15 @@ getUserAttributes(attributes) { | ||
const state = generateState(); | ||
const url = await this.getAuth0Provider(event).createAuthorizationURL(state); | ||
event.cookies.set(`${cookiePrefix}auth0_state`, state, { | ||
const { provider, type } = this.getOAuthProvider(event); | ||
let url; | ||
let codeVerifier = null; | ||
switch (type) { | ||
case 'auth0': | ||
url = provider.createAuthorizationURL(state, codeVerifier, []); | ||
break; | ||
case 'entra': | ||
codeVerifier = generateCodeVerifier(); | ||
url = provider.createAuthorizationURL(state, codeVerifier, []); | ||
break; | ||
} | ||
event.cookies.set(`${cookiePrefix}${type}_state`, state, { | ||
path: '/', | ||
@@ -105,2 +118,10 @@ httpOnly: true, | ||
}); | ||
if (codeVerifier) { | ||
event.cookies.set(`${cookiePrefix}${type}_verifier`, codeVerifier, { | ||
path: '/', | ||
httpOnly: true, | ||
maxAge: 60 * 10, | ||
secure: useSecureCookie, | ||
}); | ||
} | ||
let returnTo = event.url.searchParams.get('return_to'); | ||
@@ -178,3 +199,12 @@ if (returnTo) { | ||
// Redirect user to log out of Auth0 too. | ||
const oidcLogoutURL = this.getAuth0Provider(event).oidcLogoutURL(session.id_token, returnTo); | ||
const { provider, type } = this.getOAuthProvider(event); | ||
let oidcLogoutURL; | ||
switch (type) { | ||
case 'auth0': | ||
oidcLogoutURL = provider.oidcLogoutURL(session.id_token, returnTo); | ||
break; | ||
case 'entra': | ||
oidcLogoutURL = provider.oauthLogoutURL(session.id_token, returnTo); | ||
break; | ||
} | ||
redirect(303, oidcLogoutURL); | ||
@@ -199,18 +229,22 @@ } | ||
async handleAuthCallback(event) { | ||
const { cookiePrefix } = shouldSecureCookie(event); | ||
const { cookies, url } = event; | ||
const storedState = cookies.get(`${cookiePrefix}auth0_state`); | ||
const state = url.searchParams.get('state'); | ||
const code = url.searchParams.get('code'); | ||
if (storedState === undefined || code === null || storedState !== state) { | ||
return new Response('invalid request', { status: 400 }); | ||
} | ||
const returnTo = cookies.get(`${cookiePrefix}auth_return`); | ||
const { type, provider } = this.getOAuthProvider(event); | ||
const cookie = shouldSecureCookie(event); | ||
const returnTo = event.cookies.get(`${cookie.cookiePrefix}auth_return`); | ||
if (returnTo != null) { | ||
cookies.delete(`${cookiePrefix}auth_return`, { path: '/' }); | ||
event.cookies.delete(`${cookie.cookiePrefix}auth_return`, { path: '/' }); | ||
} | ||
cookies.delete(`${cookiePrefix}auth0_state`, { path: '/' }); | ||
let tokens = null; | ||
switch (type) { | ||
case 'auth0': | ||
tokens = await provider.handleAuthCallback(event, cookie); | ||
break; | ||
case 'entra': | ||
tokens = await provider.handleAuthCallback(event, cookie); | ||
break; | ||
} | ||
try { | ||
const tokens = await this.getAuth0Provider(event).validateAuthorizationCode(code); | ||
const idToken = parseJWT(tokens.idToken); | ||
if (!tokens) { | ||
throw new Error('Missing tokens'); | ||
} | ||
const idToken = parseJWT(tokens.idToken()); | ||
if (!idToken) { | ||
@@ -223,4 +257,4 @@ throw new Error('Missing or invalid id token'); | ||
userID: user.id, | ||
accessToken: tokens.accessToken, | ||
idToken: tokens.idToken, | ||
accessToken: tokens.accessToken(), | ||
idToken: tokens.idToken(), | ||
}); | ||
@@ -233,4 +267,4 @@ const sessionCookie = this.createSessionCookie(session.id); | ||
userID: user.id, | ||
accessToken: tokens.accessToken, | ||
idToken: tokens.idToken, | ||
accessToken: tokens.accessToken(), | ||
idToken: tokens.idToken(), | ||
}, event); | ||
@@ -356,9 +390,24 @@ } | ||
} | ||
function auth0ProviderGetter(opts) { | ||
let auth0 = undefined; | ||
function oauthProviderGetter(providerOptions) { | ||
let provider = undefined; | ||
const type = providerOptions.type; | ||
return (event) => { | ||
if (auth0 !== undefined) { | ||
return auth0; | ||
if (provider !== undefined) { | ||
return { | ||
type, | ||
provider, | ||
}; | ||
} | ||
return (auth0 = new Auth0(opts, event.url)); | ||
switch (providerOptions.type) { | ||
case 'auth0': | ||
provider = new Auth0(providerOptions.options, event.url); | ||
break; | ||
case 'entra': | ||
provider = new MicrosoftEntraId(providerOptions.options, event.url); | ||
break; | ||
} | ||
return { | ||
type, | ||
provider, | ||
}; | ||
}; | ||
@@ -365,0 +414,0 @@ } |
@@ -1,2 +0,3 @@ | ||
import { Auth0 as Base } from 'arctic'; | ||
import type { RequestEvent } from '@sveltejs/kit'; | ||
import { Auth0 as Base, OAuth2Tokens } from 'arctic'; | ||
export type Auth0ProviderOptions = { | ||
@@ -15,6 +16,8 @@ domain: string; | ||
constructor(opts: Auth0ProviderOptions, baseURL: URL | string); | ||
createAuthorizationURL(state: string, opts?: { | ||
scopes?: string[]; | ||
}): Promise<URL>; | ||
createAuthorizationURL(state: string, codeVerifier: string | null, scopes: string[]): URL; | ||
oidcLogoutURL(idToken: string, redirectURL: URL | string): URL; | ||
handleAuthCallback(event: RequestEvent, cookie: { | ||
useSecureCookie: boolean; | ||
cookiePrefix: string; | ||
}): Promise<OAuth2Tokens | null>; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { Auth0 as Base } from 'arctic'; | ||
import { Auth0 as Base, OAuth2Tokens } from 'arctic'; | ||
export class Auth0 extends Base { | ||
@@ -9,12 +9,10 @@ opts; | ||
} | ||
async createAuthorizationURL(state, opts) { | ||
const url = await super.createAuthorizationURL(state, { | ||
scopes: [ | ||
'openid', | ||
'profile', | ||
'email', | ||
...(this.opts.additionalScopes ?? []), | ||
...(opts?.scopes ?? []), | ||
], | ||
}); | ||
createAuthorizationURL(state, codeVerifier, scopes) { | ||
const url = super.createAuthorizationURL(state, codeVerifier, [ | ||
'openid', | ||
'profile', | ||
'email', | ||
...(this.opts.additionalScopes ?? []), | ||
...(scopes ?? []), | ||
]); | ||
if (this.opts.audience !== undefined) | ||
@@ -32,2 +30,19 @@ url.searchParams.set('audience', this.opts.audience); | ||
} | ||
async handleAuthCallback(event, cookie) { | ||
const { cookies, url } = event; | ||
const { cookiePrefix } = cookie; | ||
const storedState = cookies.get(`${cookiePrefix}auth0_state`); | ||
const state = url.searchParams.get('state'); | ||
const code = url.searchParams.get('code'); | ||
if (storedState === undefined || code === null || storedState !== state) { | ||
return null; | ||
} | ||
cookies.delete(`${cookiePrefix}auth0_state`, { path: '/' }); | ||
try { | ||
return await this.validateAuthorizationCode(code, null); | ||
} | ||
catch (e) { | ||
return null; | ||
} | ||
} | ||
} |
{ | ||
"name": "@peopleplus/auth", | ||
"version": "0.0.0-snapshot-20250110123508", | ||
"version": "0.0.0-snapshot-20250116161436", | ||
"scripts": { | ||
@@ -54,5 +54,6 @@ "dev": "vite dev", | ||
"@sveltejs/vite-plugin-svelte": "^3.0.0", | ||
"@types/eslint": "8.56.10", | ||
"@types/eslint": "8.56.12", | ||
"@typescript-eslint/eslint-plugin": "^7.6.0", | ||
"@typescript-eslint/parser": "^7.6.0", | ||
"arctic": "^3.1.3", | ||
"autoprefixer": "^10.4.16", | ||
@@ -64,12 +65,12 @@ "eslint": "^8.56.0", | ||
"postgres": "^3.4.3", | ||
"prettier": "~3.2.4", | ||
"prettier": "~3.4.2", | ||
"prettier-plugin-svelte": "~3.2.1", | ||
"publint": "^0.2.7", | ||
"publint": "^0.3.1", | ||
"svelte": "^4.2.7", | ||
"svelte-check": "^3.6.0", | ||
"svelte-check": "^4.1.3", | ||
"tailwindcss": "^3.4.1", | ||
"tslib": "~2.6.2", | ||
"typescript": "~5.4.2", | ||
"typescript": "~5.5.4", | ||
"vite": "^5.0.3", | ||
"vitest": "^1.0.0" | ||
"vitest": "^2.0.5" | ||
}, | ||
@@ -82,3 +83,2 @@ "main": "./dist/index.js", | ||
"@opentelemetry/api": "^1.0.0", | ||
"arctic": "^1.0.0", | ||
"esm-env": "^1.0.0", | ||
@@ -85,0 +85,0 @@ "lucia": "^3.0.0", |
44388
11.47%7
-12.5%14
16.67%943
16.42%26
4%- Removed
- Removed
- Removed