From 3d5f1ceadef28fb15a238d0a04737ff70c39df8f Mon Sep 17 00:00:00 2001 From: Steel <mael.acier@ensiie.fr> Date: Fri, 13 Sep 2024 15:38:10 +0200 Subject: [PATCH] 0.5.0: adapters --- .gitignore | 2 + package.json | 8 +-- src/app.d.ts | 3 +- src/auth.ts | 6 ++- .../{default.ts => adapters/sqlite-memory.ts} | 26 +++------ src/lib/index.ts | 54 ++++++++++++++----- src/lib/lucia.ts | 21 ++------ src/lib/types.ts | 35 +++++++++--- 8 files changed, 96 insertions(+), 59 deletions(-) rename src/lib/{default.ts => adapters/sqlite-memory.ts} (80%) diff --git a/.gitignore b/.gitignore index ac7211b..fe96ad8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ node_modules !.env.example vite.config.js.timestamp-* vite.config.ts.timestamp-* + +*.tgz diff --git a/package.json b/package.json index 6fb2ffb..f4ec138 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@arise/aidc-sveltekit", - "version": "0.4.1", + "version": "0.5.0", "type": "module", "scripts": { "dev": "vite dev", @@ -21,9 +21,9 @@ "types": "./dist/index.d.ts", "svelte": "./dist/index.js" }, - "./default": { - "types": "./dist/default.d.ts", - "svelte": "./dist/default.js" + "./adapters/*": { + "types": "./dist/adapters/*.d.ts", + "svelte": "./dist/adapters/*.js" }, "./lucia": { "types": "./dist/lucia.d.ts", diff --git a/src/app.d.ts b/src/app.d.ts index 20ce246..a979ab2 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,10 +1,11 @@ // See https://kit.svelte.dev/docs/types#app import type { Locals as AriseLocals } from "$lib/types.ts"; +import type { aidc } from "./auth.ts"; declare global { namespace App { - interface Locals extends AriseLocals {} + interface Locals extends AriseLocals<typeof aidc> {} } } diff --git a/src/auth.ts b/src/auth.ts index acc1d46..9c41a2f 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,11 +1,13 @@ import { env } from "$env/dynamic/private"; import { AriseIdConnect } from "$lib/index.js"; -import { defaultLucia } from "$lib/default.js"; +import { sqliteMemoryAdapter } from "$lib/adapters/sqlite-memory.js"; export const aidc = await AriseIdConnect.init({ client_id: env.AIDC_CLIENT_ID!, client_secret: env.AIDC_CLIENT_SECRET!, scope: "openid offline profile", - wrapper: defaultLucia, + adapter: sqliteMemoryAdapter, // issuer: "http://localhost:4444/.well-known/openid-configuration" }); + +sqliteMemoryAdapter.initDatabase(); diff --git a/src/lib/default.ts b/src/lib/adapters/sqlite-memory.ts similarity index 80% rename from src/lib/default.ts rename to src/lib/adapters/sqlite-memory.ts index 4ded991..19aa4ba 100644 --- a/src/lib/default.ts +++ b/src/lib/adapters/sqlite-memory.ts @@ -2,15 +2,18 @@ import { BetterSqlite3Adapter } from "@lucia-auth/adapter-sqlite"; import sqlite from "better-sqlite3"; import type { Database as SqLiteConnection } from "better-sqlite3"; import { - LuciaWrapper, + LuciaAdapter, type DefaultSessionAttributes, type DefaultUserAttributes, -} from "./lucia.js"; +} from "../lucia.js"; import type { IdTokenClaims, UserinfoResponse } from "openid-client"; -class DefaultLucia< +class SqliteMemoryLuciaAdapter< UserInfo extends Record<string, never> = Record<string, never>, -> extends LuciaWrapper<Omit<DatabaseUser<UserInfo>, "id">> { +> extends LuciaAdapter< + DefaultSessionAttributes, + Omit<DatabaseUser<UserInfo>, "id"> +> { db: SqLiteConnection; constructor() { @@ -63,21 +66,8 @@ class DefaultLucia< } } -export const defaultLucia = new DefaultLucia(); +export const sqliteMemoryAdapter = new SqliteMemoryLuciaAdapter(); -declare module "lucia" { - interface Register { - Lucia: typeof defaultLucia.lucia; - DatabaseUserAttributes: Omit<InternalUser, "id">; - DatabaseSessionAttributes: Omit<DatabaseSession, "id">; - } -} - -interface InternalUser { - id: string; - subject: string; - claims: string; -} export interface DatabaseUser<T extends Record<string, never>> extends DefaultUserAttributes { id: string; diff --git a/src/lib/index.ts b/src/lib/index.ts index 40e520e..f35f9de 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,6 +1,6 @@ import { redirect, type Handle, error, type RequestEvent } from "@sveltejs/kit"; import { sequence } from "@sveltejs/kit/hooks"; -import { generateId } from "lucia"; +import { generateId, type Session } from "lucia"; import type { Client, TokenSet } from "openid-client"; import { Issuer, errors, generators } from "openid-client"; import { SEE_OTHER } from "readable-http-codes"; @@ -12,14 +12,41 @@ import { addBasePath, } from "./helpers.js"; import type { Config, CookieNames, Paths } from "./types.js"; +import type { + DefaultSessionAttributes, + DefaultUserAttributes, + LuciaAdapter, +} from "./lucia.js"; + +interface InternalUser { + id: string; + subject: string; + claims: string; +} + +export interface DatabaseSession extends DefaultSessionAttributes { + id: string; + id_token: string; +} + +declare module "lucia" { + interface Register { + Lucia: LuciaAdapter; + DatabaseUserAttributes: Omit<InternalUser, "id">; + DatabaseSessionAttributes: Omit<DatabaseSession, "id">; + } +} -export class AriseIdConnect { +export class AriseIdConnect< + SessionAttributes extends DefaultSessionAttributes, + UserAttributes extends DefaultUserAttributes, +> { readonly client: Client; readonly paths: Paths; protected cookieNames: CookieNames; constructor( - readonly config: Config, + readonly config: Config<SessionAttributes, UserAttributes>, issuer: Issuer, ) { this.client = new issuer.Client({ @@ -43,14 +70,16 @@ export class AriseIdConnect { }; } - static async init(config: Config): Promise<AriseIdConnect> { + static async init< + Session extends DefaultSessionAttributes, + User extends DefaultUserAttributes, + >(config: Config<Session, User>): Promise<AriseIdConnect<Session, User>> { const issuer = await Issuer.discover( config.issuer || "https://oidc.iiens.net/.well-known/openid-configuration", ); const aidc = new AriseIdConnect(config, issuer); - await aidc.config.wrapper.initDatabase(); return aidc; } @@ -118,14 +147,15 @@ export class AriseIdConnect { redirect(SEE_OTHER, this.paths.home); } - const { lucia } = this.config.wrapper; + const { adapter: lucia } = this.config; const { session } = await lucia.validateSession(event.locals.session.id); + const typedSession = session as SessionAttributes | null; const postLogoutRedirectURI = new URL(this.paths.logoutCallback, event.url); const endSessionUrl = this.client.endSessionUrl({ post_logout_redirect_uri: postLogoutRedirectURI.toString(), - id_token_hint: session?.id_token, + id_token_hint: typedSession?.id_token, }); if (this.config.on?.logout) { @@ -148,7 +178,7 @@ export class AriseIdConnect { const claims = tokenSet.claims(); const { sub } = claims; - const { wrapper } = this.config; + const { adapter: wrapper } = this.config; const existingUserId = await wrapper.getUserId(sub); let userId = existingUserId ?? generateId(15); @@ -157,10 +187,10 @@ export class AriseIdConnect { userId = await wrapper.createUser(sub, userId, claims); } - const session = await wrapper.lucia.createSession(userId, { + const session = await wrapper.createSession(userId, { id_token: tokenSet.id_token, }); - setLuciaCookie(event, wrapper.lucia.createSessionCookie(session.id)); + setLuciaCookie(event, wrapper.createSessionCookie(session.id)); if (this.config.on?.login) { return this.config.on.login(event, tokenSet.claims()); @@ -170,7 +200,7 @@ export class AriseIdConnect { }; protected sessionHandler: Handle = async ({ event, resolve }) => { - const { lucia } = this.config.wrapper; + const { adapter: lucia } = this.config; const sessionId = event.cookies.get(lucia.sessionCookieName); if (!sessionId) { @@ -189,7 +219,7 @@ export class AriseIdConnect { } event.locals.user = user; - event.locals.session = session; + event.locals.session = session as (SessionAttributes & Session) | null; return resolve(event); }; diff --git a/src/lib/lucia.ts b/src/lib/lucia.ts index 79a2f71..2763c0c 100644 --- a/src/lib/lucia.ts +++ b/src/lib/lucia.ts @@ -18,18 +18,16 @@ export interface DatabaseSession { id_token: string; } -export type DefaultUserAttributes = Record<string, unknown>; +export type DefaultUserAttributes = Record<never, never>; export interface DefaultSessionAttributes { id_token: string; } -export abstract class LuciaWrapper< - _UserAttributes extends DefaultUserAttributes = DefaultUserAttributes, +export abstract class LuciaAdapter< _SessionAttributes extends DefaultSessionAttributes = DefaultSessionAttributes, -> { - lucia: Lucia<_SessionAttributes, _UserAttributes>; - + _UserAttributes extends DefaultUserAttributes = DefaultUserAttributes, +> extends Lucia<_SessionAttributes, _UserAttributes> { constructor( adapter: Adapter, options?: { @@ -43,26 +41,17 @@ export abstract class LuciaWrapper< ) => _UserAttributes; }, ) { - this.lucia = new Lucia(adapter, { + super(adapter, { sessionCookie: { attributes: { secure: !dev, }, name: "aidc_session", }, - getUserAttributes() { - return {}; - }, - getSessionAttributes(attributes) { - return { - id_token: attributes.id_token, - }; - }, ...options, }); } - initDatabase(): MaybePromise<void> {} abstract getUserId(subject: string): MaybePromise<string | undefined>; abstract createUser( subject: string, diff --git a/src/lib/types.ts b/src/lib/types.ts index d664534..44d55e4 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,8 +1,17 @@ import type { MaybePromise, RequestEvent } from "@sveltejs/kit"; import type { ClientMetadata, UserinfoResponse, errors } from "openid-client"; -import type { LuciaWrapper } from "./lucia.js"; +import type { + DefaultSessionAttributes, + DefaultUserAttributes, + LuciaAdapter, +} from "./lucia.js"; +import { AriseIdConnect } from "./index.js"; +import type { Session, User } from "lucia"; -export interface Config extends ClientMetadata { +export interface Config< + Session extends DefaultSessionAttributes, + User extends DefaultUserAttributes, +> extends ClientMetadata { client_secret: string; scope: string; issuer?: string; @@ -11,7 +20,7 @@ export interface Config extends ClientMetadata { login?: (event: RequestEvent, userInfo: UserinfoResponse) => never; logout?: (event: RequestEvent) => MaybePromise<void>; }; - wrapper: LuciaWrapper; + adapter: LuciaAdapter<Session, User>; cookieNames?: Partial<CookieNames>; paths?: Partial<Paths>; } @@ -29,8 +38,22 @@ export type CookieNames = { oauthCodeVerifier: string; }; -export interface Locals { - user: import("lucia").User | null; - session: import("lucia").Session | null; +export interface Locals< + T extends AriseIdConnect<DefaultSessionAttributes, DefaultUserAttributes>, +> { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + user: + | ((T extends AriseIdConnect<any, infer UserAttributes> + ? UserAttributes + : never) & + User) + | null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + session: + | ((T extends AriseIdConnect<infer SessionAttributes, any> + ? SessionAttributes + : never) & + Session) + | null; authPaths: Paths; } -- GitLab