From ddd6ffe8c6c304515d809ebc4072cd02b7bee3d1 Mon Sep 17 00:00:00 2001 From: dswbx Date: Mon, 7 Jul 2025 09:06:44 +0200 Subject: [PATCH] init auth strategy registry --- app/src/auth/AppAuth.ts | 5 ++- app/src/auth/auth-schema.ts | 39 ++++++++++--------- .../strategies/oauth/CustomOAuthStrategy.ts | 2 +- .../strategies/oauth/OAuthStrategy.ts | 2 +- app/src/core/index.ts | 2 +- app/src/core/registry/Registry.ts | 2 +- app/src/media/AppMedia.ts | 4 +- app/src/media/index.ts | 26 ------------- app/src/media/media-schema.ts | 20 ++++++---- app/src/modules/registries.ts | 4 +- app/src/ui/routes/auth/auth.strategies.tsx | 3 +- 11 files changed, 45 insertions(+), 64 deletions(-) diff --git a/app/src/auth/AppAuth.ts b/app/src/auth/AppAuth.ts index 992f8a4d..334a5216 100644 --- a/app/src/auth/AppAuth.ts +++ b/app/src/auth/AppAuth.ts @@ -6,7 +6,7 @@ import type { Entity, EntityManager } from "data"; import { em, entity, enumm, type FieldSchema } from "data/prototype"; import { Module } from "modules/Module"; import { AuthController } from "./api/AuthController"; -import { type AppAuthSchema, authConfigSchema, STRATEGIES } from "./auth-schema"; +import { type AppAuthSchema, authConfigSchema, AuthStrategyRegistry } from "./auth-schema"; import { AppUserPool } from "auth/AppUserPool"; import type { AppEntity } from "core/config"; import { usersFields } from "./auth-entities"; @@ -65,7 +65,8 @@ export class AppAuth extends Module { // build strategies const strategies = transformObject(this.config.strategies ?? {}, (strategy, name) => { try { - return new STRATEGIES[strategy.type].cls(strategy.config as any); + const cls = AuthStrategyRegistry.get(strategy.type as any).cls; + return new cls(strategy.config as any); } catch (e) { throw new Error( `Could not build strategy ${String( diff --git a/app/src/auth/auth-schema.ts b/app/src/auth/auth-schema.ts index 940d0e83..e7b79dc0 100644 --- a/app/src/auth/auth-schema.ts +++ b/app/src/auth/auth-schema.ts @@ -1,24 +1,25 @@ -import { cookieConfig, jwtConfig } from "auth/authenticate/Authenticator"; -import { CustomOAuthStrategy, OAuthStrategy, PasswordStrategy } from "auth/authenticate/strategies"; +import { cookieConfig, jwtConfig, type Strategy } from "auth/authenticate/Authenticator"; import { objectTransform } from "core/utils"; import { s } from "core/object/schema"; +import type { ProvidedOAuthConfig } from "./authenticate/strategies/oauth/OAuthStrategy"; +import type { OAuthConfigCustom } from "./authenticate/strategies/oauth/CustomOAuthStrategy"; +import { Registry, type Constructor } from "core"; +import { PasswordStrategy } from "./authenticate/strategies/PasswordStrategy"; +import { OAuthStrategy } from "./authenticate/strategies/oauth/OAuthStrategy"; +import { CustomOAuthStrategy } from "./authenticate/strategies/oauth/CustomOAuthStrategy"; -export const Strategies = { - password: { - cls: PasswordStrategy, - schema: PasswordStrategy.prototype.getSchema(), - }, - oauth: { - cls: OAuthStrategy, - schema: OAuthStrategy.prototype.getSchema(), - }, - custom_oauth: { - cls: CustomOAuthStrategy, - schema: CustomOAuthStrategy.prototype.getSchema(), - }, -} as const; +export const AuthStrategyRegistry = new Registry<{ + cls: Constructor; + schema: s.Schema; +}>((cls: Constructor) => ({ + cls, + schema: cls.prototype.getSchema() as s.Schema, +})) + .register("password", PasswordStrategy) + .register("oauth", OAuthStrategy) + .register("custom_oauth", CustomOAuthStrategy); -export const STRATEGIES = Strategies; +export const STRATEGIES = AuthStrategyRegistry.all(); const strategiesSchemaObject = objectTransform(STRATEGIES, (strategy, name) => { return s.strictObject( { @@ -34,8 +35,8 @@ const strategiesSchemaObject = objectTransform(STRATEGIES, (strategy, name) => { const strategiesSchema = s.anyOf(Object.values(strategiesSchemaObject)); export type AppAuthStrategies = s.Static; -export type AppAuthOAuthStrategy = s.Static; -export type AppAuthCustomOAuthStrategy = s.Static; +export type AppAuthOAuthStrategy = ProvidedOAuthConfig; +export type AppAuthCustomOAuthStrategy = OAuthConfigCustom; const guardConfigSchema = s.object({ enabled: s.boolean({ default: false }).optional(), diff --git a/app/src/auth/authenticate/strategies/oauth/CustomOAuthStrategy.ts b/app/src/auth/authenticate/strategies/oauth/CustomOAuthStrategy.ts index fcb2f7cf..73f6bc20 100644 --- a/app/src/auth/authenticate/strategies/oauth/CustomOAuthStrategy.ts +++ b/app/src/auth/authenticate/strategies/oauth/CustomOAuthStrategy.ts @@ -30,7 +30,7 @@ const oauthSchemaCustom = s.strictObject( { title: "Custom OAuth" }, ); -type OAuthConfigCustom = s.Static; +export type OAuthConfigCustom = s.Static; export type UserProfile = { sub: string; diff --git a/app/src/auth/authenticate/strategies/oauth/OAuthStrategy.ts b/app/src/auth/authenticate/strategies/oauth/OAuthStrategy.ts index af09e877..b4d770db 100644 --- a/app/src/auth/authenticate/strategies/oauth/OAuthStrategy.ts +++ b/app/src/auth/authenticate/strategies/oauth/OAuthStrategy.ts @@ -26,7 +26,7 @@ const schemaProvided = s.object( }, { title: "OAuth" }, ); -type ProvidedOAuthConfig = s.Static; +export type ProvidedOAuthConfig = s.Static; export type CustomOAuthConfig = { type: SupportedTypes; diff --git a/app/src/core/index.ts b/app/src/core/index.ts index fae1eb2b..8ad4507b 100644 --- a/app/src/core/index.ts +++ b/app/src/core/index.ts @@ -23,7 +23,7 @@ export { type BooleanLike, isBooleanLike, } from "./object/query/query"; -export { Registry, type Constructor } from "./registry/Registry"; +export { Registry, type Constructor, type ClassThatImplements } from "./registry/Registry"; export { getFlashMessage } from "./server/flash"; /* export { s, diff --git a/app/src/core/registry/Registry.ts b/app/src/core/registry/Registry.ts index 7c56070d..964ad26b 100644 --- a/app/src/core/registry/Registry.ts +++ b/app/src/core/registry/Registry.ts @@ -1,5 +1,5 @@ export type Constructor = new (...args: any[]) => T; - +export type ClassThatImplements = Constructor & { prototype: T }; export type RegisterFn = (unknown: any) => Item; export class Registry< diff --git a/app/src/media/AppMedia.ts b/app/src/media/AppMedia.ts index aaf31db1..686b4d2e 100644 --- a/app/src/media/AppMedia.ts +++ b/app/src/media/AppMedia.ts @@ -5,7 +5,7 @@ import { type FileUploadedEventData, Storage, type StorageAdapter, MediaPermissi import { Module } from "modules/Module"; import { type FieldSchema, em, entity } from "../data/prototype"; import { MediaController } from "./api/MediaController"; -import { buildMediaSchema, type mediaConfigSchema, registry, type TAppMediaConfig } from "./media-schema"; +import { buildMediaSchema, MediaAdapterRegistry, type TAppMediaConfig } from "./media-schema"; import { mediaFields } from "./media-entities"; export type MediaFieldSchema = FieldSchema; @@ -35,7 +35,7 @@ export class AppMedia extends Module> { let adapter: StorageAdapter; try { const { type, config } = this.config.adapter; - const cls = registry.get(type as any).cls; + const cls = MediaAdapterRegistry.get(type as any).cls; adapter = new cls(config as any); this._storage = new Storage(adapter, this.config.storage, this.ctx.emgr); diff --git a/app/src/media/index.ts b/app/src/media/index.ts index ce3d9b00..c4610c56 100644 --- a/app/src/media/index.ts +++ b/app/src/media/index.ts @@ -1,5 +1,3 @@ -import { type Constructor, Registry } from "core"; - export { guess as guessMimeType } from "./storage/mime-types-tiny"; export { Storage, @@ -15,7 +13,6 @@ import { StorageCloudinaryAdapter, } from "./storage/adapters/cloudinary/StorageCloudinaryAdapter"; import { type S3AdapterConfig, StorageS3Adapter } from "./storage/adapters/s3/StorageS3Adapter"; -import type { s } from "core/object/schema"; export { StorageAdapter }; export { StorageS3Adapter, type S3AdapterConfig, StorageCloudinaryAdapter, type CloudinaryConfig }; @@ -25,27 +22,4 @@ export * as MediaPermissions from "./media-permissions"; export type { FileUploadedEventData } from "./storage/events"; export * from "./utils"; -type ClassThatImplements = Constructor & { prototype: T }; - -export const MediaAdapterRegistry = new Registry<{ - cls: ClassThatImplements; - schema: s.Schema; -}>((cls: ClassThatImplements) => ({ - cls, - schema: cls.prototype.getSchema() as s.Schema, -})) - .register("s3", StorageS3Adapter) - .register("cloudinary", StorageCloudinaryAdapter); - -export const Adapters = { - s3: { - cls: StorageS3Adapter, - schema: StorageS3Adapter.prototype.getSchema(), - }, - cloudinary: { - cls: StorageCloudinaryAdapter, - schema: StorageCloudinaryAdapter.prototype.getSchema(), - }, -} as const; - export { adapterTestSuite } from "./storage/adapters/adapter-test-suite"; diff --git a/app/src/media/media-schema.ts b/app/src/media/media-schema.ts index 6c9bdae0..d4bfb9e2 100644 --- a/app/src/media/media-schema.ts +++ b/app/src/media/media-schema.ts @@ -1,16 +1,20 @@ import { objectTransform } from "core/utils"; -import { Adapters } from "media"; -import { registries } from "modules/registries"; +import { type StorageAdapter, StorageS3Adapter, StorageCloudinaryAdapter } from "media"; import { s } from "core/object/schema"; +import { Registry, type ClassThatImplements } from "core"; -export const ADAPTERS = { - ...Adapters, -} as const; - -export const registry = registries.media; +export const MediaAdapterRegistry = new Registry<{ + cls: ClassThatImplements; + schema: s.Schema; +}>((cls: ClassThatImplements) => ({ + cls, + schema: cls.prototype.getSchema() as s.Schema, +})) + .register("s3", StorageS3Adapter) + .register("cloudinary", StorageCloudinaryAdapter); export function buildMediaSchema() { - const adapterSchemaObject = objectTransform(registry.all(), (adapter, name) => { + const adapterSchemaObject = objectTransform(MediaAdapterRegistry.all(), (adapter, name) => { return s.strictObject( { type: s.literal(name), diff --git a/app/src/modules/registries.ts b/app/src/modules/registries.ts index fdd29aa0..45ff570c 100644 --- a/app/src/modules/registries.ts +++ b/app/src/modules/registries.ts @@ -1,7 +1,9 @@ -import { MediaAdapterRegistry } from "media"; +import { AuthStrategyRegistry } from "auth/auth-schema"; +import { MediaAdapterRegistry } from "media/media-schema"; const registries = { media: MediaAdapterRegistry, + auth: AuthStrategyRegistry, } as const; export { registries }; diff --git a/app/src/ui/routes/auth/auth.strategies.tsx b/app/src/ui/routes/auth/auth.strategies.tsx index 2aa0c4f9..ca047f6a 100644 --- a/app/src/ui/routes/auth/auth.strategies.tsx +++ b/app/src/ui/routes/auth/auth.strategies.tsx @@ -13,7 +13,6 @@ import { TbBrandX, TbSettings, } from "react-icons/tb"; -import { twMerge } from "tailwind-merge"; import { useBknd } from "ui/client/bknd"; import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth"; import { Button } from "ui/components/buttons/Button"; @@ -76,7 +75,7 @@ function AuthStrategiesListInternal() { return (