diff --git a/.changeset/fluffy-toys-ring.md b/.changeset/fluffy-toys-ring.md new file mode 100644 index 000000000..67facfca6 --- /dev/null +++ b/.changeset/fluffy-toys-ring.md @@ -0,0 +1,5 @@ +--- +'@baseplate-dev/react-generators': patch +--- + +Remove @headlessui/react package diff --git a/.changeset/rare-pandas-burn.md b/.changeset/rare-pandas-burn.md new file mode 100644 index 000000000..75682f416 --- /dev/null +++ b/.changeset/rare-pandas-burn.md @@ -0,0 +1,5 @@ +--- +'@baseplate-dev/plugin-auth': patch +--- + +Support validating users on admin app based off their roles diff --git a/packages/core-generators/src/renderers/typescript/extractor/build-ts-project-export-map.ts b/packages/core-generators/src/renderers/typescript/extractor/build-ts-project-export-map.ts index 12985956a..8253dbfbd 100644 --- a/packages/core-generators/src/renderers/typescript/extractor/build-ts-project-export-map.ts +++ b/packages/core-generators/src/renderers/typescript/extractor/build-ts-project-export-map.ts @@ -99,7 +99,10 @@ export function buildTsProjectExportMap( }) ?? []; // Figure out the default import provider - const importProviderNames = getDefaultImportProviderNames(generatorName); + const importProviderNames = getDefaultImportProviderNames( + generatorName, + config.defaultImportProviderName, + ); const relativeGeneratorDirectory = path.relative( packagePath, diff --git a/packages/core-generators/src/renderers/typescript/extractor/default-import-providers.ts b/packages/core-generators/src/renderers/typescript/extractor/default-import-providers.ts index bfe159d65..93fb7ed7f 100644 --- a/packages/core-generators/src/renderers/typescript/extractor/default-import-providers.ts +++ b/packages/core-generators/src/renderers/typescript/extractor/default-import-providers.ts @@ -30,13 +30,17 @@ export interface TsImportProviderNames { * Gets the names of the default import provider. * * @param generatorName - The name of the generator. + * @param defaultImportProviderName - The name of the default import provider if provided. * @returns The names of the import provider. */ export function getDefaultImportProviderNames( generatorName: string, + defaultImportProviderName: string | undefined, ): TsImportProviderNames { const parsedGeneratorName = parseGeneratorName(generatorName); - const { generatorBasename } = parsedGeneratorName; + const generatorBasename = + defaultImportProviderName?.replace(/ImportsProvider$/, '') ?? + parsedGeneratorName.generatorBasename; const providerTypeName = `${pascalCase(generatorBasename)}ImportsProvider`; const providerExportName = `${camelCase(generatorBasename)}ImportsProvider`; const providerSchemaName = `${camelCase(generatorBasename)}ImportsSchema`; diff --git a/packages/core-generators/src/renderers/typescript/extractor/render-ts-import-providers.ts b/packages/core-generators/src/renderers/typescript/extractor/render-ts-import-providers.ts index 63c52bfea..c0df2f4ec 100644 --- a/packages/core-generators/src/renderers/typescript/extractor/render-ts-import-providers.ts +++ b/packages/core-generators/src/renderers/typescript/extractor/render-ts-import-providers.ts @@ -267,7 +267,10 @@ export function renderTsImportProviders( generatorBarrelExports: TemplateExtractorGeneratedBarrelExport[]; } | undefined { - const importProviderNames = getDefaultImportProviderNames(generatorName); + const importProviderNames = getDefaultImportProviderNames( + generatorName, + extractorConfig?.defaultImportProviderName, + ); const projectExportArray = templates.flatMap((template) => Object.entries(template.config.projectExports ?? {}).map( diff --git a/packages/core-generators/src/renderers/typescript/extractor/ts-extractor-config.schema.ts b/packages/core-generators/src/renderers/typescript/extractor/ts-extractor-config.schema.ts index b7dc4cd4a..a869bb983 100644 --- a/packages/core-generators/src/renderers/typescript/extractor/ts-extractor-config.schema.ts +++ b/packages/core-generators/src/renderers/typescript/extractor/ts-extractor-config.schema.ts @@ -22,6 +22,13 @@ export const tsExtractorConfigSchema = z * Always specified as package-name:provider-name */ importProviders: z.array(z.string()).optional(), + /** + * Optional, the name of the default import provider to use (must end with 'ImportsProvider'). Defaults to the generator name with ImportsProvider suffix. + */ + defaultImportProviderName: z + .string() + .endsWith('ImportsProvider') + .optional(), /** * Optional, whether to skip the default import map generation */ diff --git a/packages/fastify-generators/src/generators/auth/auth-roles/extractor.json b/packages/fastify-generators/src/generators/auth/auth-roles/extractor.json index a12dc0c3d..ff08cef77 100644 --- a/packages/fastify-generators/src/generators/auth/auth-roles/extractor.json +++ b/packages/fastify-generators/src/generators/auth/auth-roles/extractor.json @@ -8,6 +8,7 @@ "importMapProviders": {}, "pathRootRelativePath": "{module-root}/constants/auth-roles.constants.ts", "projectExports": { + "AUTH_ROLE_CONFIG": {}, "AuthRole": { "isTypeOnly": true }, "DEFAULT_PUBLIC_ROLES": {}, "DEFAULT_USER_ROLES": {}, diff --git a/packages/fastify-generators/src/generators/auth/auth-roles/generated/ts-import-providers.ts b/packages/fastify-generators/src/generators/auth/auth-roles/generated/ts-import-providers.ts index 6bcdf3e84..df243d877 100644 --- a/packages/fastify-generators/src/generators/auth/auth-roles/generated/ts-import-providers.ts +++ b/packages/fastify-generators/src/generators/auth/auth-roles/generated/ts-import-providers.ts @@ -13,6 +13,7 @@ import { import { AUTH_AUTH_ROLES_PATHS } from './template-paths.js'; const authRolesImportsSchema = createTsImportMapSchema({ + AUTH_ROLE_CONFIG: {}, AuthRole: { isTypeOnly: true }, DEFAULT_PUBLIC_ROLES: {}, DEFAULT_USER_ROLES: {}, @@ -35,6 +36,7 @@ const authAuthRolesImportsTask = createGeneratorTask({ return { providers: { authRolesImports: createTsImportMap(authRolesImportsSchema, { + AUTH_ROLE_CONFIG: paths.authRoles, AuthRole: paths.authRoles, DEFAULT_PUBLIC_ROLES: paths.authRoles, DEFAULT_USER_ROLES: paths.authRoles, diff --git a/packages/fastify-generators/src/generators/auth/auth-roles/generated/typed-templates.ts b/packages/fastify-generators/src/generators/auth/auth-roles/generated/typed-templates.ts index efb4fceaf..e52b1b720 100644 --- a/packages/fastify-generators/src/generators/auth/auth-roles/generated/typed-templates.ts +++ b/packages/fastify-generators/src/generators/auth/auth-roles/generated/typed-templates.ts @@ -6,6 +6,7 @@ const authRoles = createTsTemplateFile({ importMapProviders: {}, name: 'auth-roles', projectExports: { + AUTH_ROLE_CONFIG: {}, AuthRole: { isTypeOnly: true }, DEFAULT_PUBLIC_ROLES: {}, DEFAULT_USER_ROLES: {}, diff --git a/packages/fastify-generators/src/generators/auth/user-session-types/extractor.json b/packages/fastify-generators/src/generators/auth/user-session-types/extractor.json index 50aac3401..bbe141a94 100644 --- a/packages/fastify-generators/src/generators/auth/user-session-types/extractor.json +++ b/packages/fastify-generators/src/generators/auth/user-session-types/extractor.json @@ -9,6 +9,10 @@ "authContextImportsProvider": { "importName": "authContextImportsProvider", "packagePathSpecifier": "@baseplate-dev/fastify-generators:src/generators/auth/auth-context/generated/ts-import-providers.ts" + }, + "authRolesImportsProvider": { + "importName": "authRolesImportsProvider", + "packagePathSpecifier": "@baseplate-dev/fastify-generators:src/generators/auth/auth-roles/generated/ts-import-providers.ts" } }, "pathRootRelativePath": "{module-root}/types/user-session.types.ts", diff --git a/packages/fastify-generators/src/generators/auth/user-session-types/generated/template-renderers.ts b/packages/fastify-generators/src/generators/auth/user-session-types/generated/template-renderers.ts index 5e73c0882..2149ba4a5 100644 --- a/packages/fastify-generators/src/generators/auth/user-session-types/generated/template-renderers.ts +++ b/packages/fastify-generators/src/generators/auth/user-session-types/generated/template-renderers.ts @@ -5,6 +5,7 @@ import { typescriptFileProvider } from '@baseplate-dev/core-generators'; import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; import { authContextImportsProvider } from '#src/generators/auth/auth-context/generated/ts-import-providers.js'; +import { authRolesImportsProvider } from '#src/generators/auth/auth-roles/generated/ts-import-providers.js'; import { AUTH_USER_SESSION_TYPES_PATHS } from './template-paths.js'; import { AUTH_USER_SESSION_TYPES_TEMPLATES } from './typed-templates.js'; @@ -30,13 +31,14 @@ const authUserSessionTypesRenderers = const authUserSessionTypesRenderersTask = createGeneratorTask({ dependencies: { authContextImports: authContextImportsProvider, + authRolesImports: authRolesImportsProvider, paths: AUTH_USER_SESSION_TYPES_PATHS.provider, typescriptFile: typescriptFileProvider, }, exports: { authUserSessionTypesRenderers: authUserSessionTypesRenderers.export(), }, - run({ authContextImports, paths, typescriptFile }) { + run({ authContextImports, authRolesImports, paths, typescriptFile }) { return { providers: { authUserSessionTypesRenderers: { @@ -47,6 +49,7 @@ const authUserSessionTypesRenderersTask = createGeneratorTask({ destination: paths.userSessionTypes, importMapProviders: { authContextImports, + authRolesImports, }, ...options, }), diff --git a/packages/fastify-generators/src/generators/auth/user-session-types/generated/typed-templates.ts b/packages/fastify-generators/src/generators/auth/user-session-types/generated/typed-templates.ts index b140b2cf4..dfa67f956 100644 --- a/packages/fastify-generators/src/generators/auth/user-session-types/generated/typed-templates.ts +++ b/packages/fastify-generators/src/generators/auth/user-session-types/generated/typed-templates.ts @@ -2,10 +2,14 @@ import { createTsTemplateFile } from '@baseplate-dev/core-generators'; import path from 'node:path'; import { authContextImportsProvider } from '#src/generators/auth/auth-context/generated/ts-import-providers.js'; +import { authRolesImportsProvider } from '#src/generators/auth/auth-roles/generated/ts-import-providers.js'; const userSessionTypes = createTsTemplateFile({ fileOptions: { kind: 'singleton' }, - importMapProviders: { authContextImports: authContextImportsProvider }, + importMapProviders: { + authContextImports: authContextImportsProvider, + authRolesImports: authRolesImportsProvider, + }, name: 'user-session-types', projectExports: { UserSessionPayload: { isTypeOnly: true }, diff --git a/packages/fastify-generators/src/generators/auth/user-session-types/templates/module/types/user-session.types.ts b/packages/fastify-generators/src/generators/auth/user-session-types/templates/module/types/user-session.types.ts index 43f7e91da..8d2b162ee 100644 --- a/packages/fastify-generators/src/generators/auth/user-session-types/templates/module/types/user-session.types.ts +++ b/packages/fastify-generators/src/generators/auth/user-session-types/templates/module/types/user-session.types.ts @@ -1,11 +1,13 @@ // @ts-nocheck import type { AuthUserSessionInfo } from '%authContextImports'; +import type { AuthRole } from '%authRolesImports'; import type { FastifyReply, FastifyRequest } from 'fastify'; export interface UserSessionPayload { userId: string; expiresAt: Date; + roles: readonly AuthRole[]; } export interface UserSessionService { diff --git a/packages/fastify-generators/src/generators/auth/user-session-types/user-session-types.generator.ts b/packages/fastify-generators/src/generators/auth/user-session-types/user-session-types.generator.ts index 68a72dfb7..0d0533dd5 100644 --- a/packages/fastify-generators/src/generators/auth/user-session-types/user-session-types.generator.ts +++ b/packages/fastify-generators/src/generators/auth/user-session-types/user-session-types.generator.ts @@ -1,8 +1,6 @@ -import { typescriptFileProvider } from '@baseplate-dev/core-generators'; import { createGenerator, createGeneratorTask } from '@baseplate-dev/sync'; import { z } from 'zod'; -import { authContextImportsProvider } from '../auth-context/index.js'; import { AUTH_USER_SESSION_TYPES_GENERATED } from './generated/index.js'; const descriptorSchema = z.object({}); @@ -14,25 +12,15 @@ export const userSessionTypesGenerator = createGenerator({ buildTasks: () => ({ paths: AUTH_USER_SESSION_TYPES_GENERATED.paths.task, imports: AUTH_USER_SESSION_TYPES_GENERATED.imports.task, + renderers: AUTH_USER_SESSION_TYPES_GENERATED.renderers.task, main: createGeneratorTask({ dependencies: { - typescriptFile: typescriptFileProvider, - paths: AUTH_USER_SESSION_TYPES_GENERATED.paths.provider, - authContextImports: authContextImportsProvider, + renderers: AUTH_USER_SESSION_TYPES_GENERATED.renderers.provider, }, - run({ typescriptFile, paths, authContextImports }) { + run({ renderers }) { return { build: async (builder) => { - await builder.apply( - typescriptFile.renderTemplateFile({ - template: - AUTH_USER_SESSION_TYPES_GENERATED.templates.userSessionTypes, - destination: paths.userSessionTypes, - importMapProviders: { - authContextImports, - }, - }), - ); + await builder.apply(renderers.userSessionTypes.render({})); }, }; }, diff --git a/packages/project-builder-lib/src/schema/apps/index.ts b/packages/project-builder-lib/src/schema/apps/index.ts index df5b783fc..9978e26fd 100644 --- a/packages/project-builder-lib/src/schema/apps/index.ts +++ b/packages/project-builder-lib/src/schema/apps/index.ts @@ -1,7 +1,4 @@ export * from './backend/index.js'; export * from './base.js'; export * from './types.js'; -// Re-export admin types from web admin for backward compatibility -export * from './web/admin/index.js'; - export * from './web/index.js'; diff --git a/packages/project-builder-lib/src/schema/apps/web/index.ts b/packages/project-builder-lib/src/schema/apps/web/index.ts index 37d1ef2cd..35d9a600d 100644 --- a/packages/project-builder-lib/src/schema/apps/web/index.ts +++ b/packages/project-builder-lib/src/schema/apps/web/index.ts @@ -1 +1,2 @@ +export * from './admin/index.js'; export * from './web-app.js'; diff --git a/packages/project-builder-lib/src/schema/apps/web/web-app.ts b/packages/project-builder-lib/src/schema/apps/web/web-app.ts index 2e0a39fe2..da9847c9c 100644 --- a/packages/project-builder-lib/src/schema/apps/web/web-app.ts +++ b/packages/project-builder-lib/src/schema/apps/web/web-app.ts @@ -2,7 +2,6 @@ import { z } from 'zod'; import type { def } from '#src/schema/creator/index.js'; -import { authRoleEntityType } from '#src/schema/auth/index.js'; import { definitionSchema } from '#src/schema/creator/schema-creator.js'; import { baseAppValidators } from '../base.js'; @@ -16,15 +15,6 @@ export const createWebAppSchema = definitionSchema((ctx) => includeAuth: ctx.withDefault(z.boolean(), false), title: z.string().default(''), description: z.string().default(''), - allowedRoles: ctx.withDefault( - z.array( - ctx.withRef({ - type: authRoleEntityType, - onDelete: 'DELETE', - }), - ), - [], - ), includeUploadComponents: ctx.withDefault(z.boolean(), false), enableSubscriptions: ctx.withDefault(z.boolean(), false), adminApp: createAdminAppSchema(ctx), diff --git a/packages/project-builder-server/src/compiler/web/index.ts b/packages/project-builder-server/src/compiler/web/index.ts index 1aba1d886..7a65b8b7a 100644 --- a/packages/project-builder-server/src/compiler/web/index.ts +++ b/packages/project-builder-server/src/compiler/web/index.ts @@ -61,7 +61,7 @@ function buildAdminRoutes( builder: AppEntryBuilder, ): GeneratorBundle | undefined { const { adminApp } = builder.appConfig; - const { projectDefinition } = builder; + const { projectDefinition, definitionContainer } = builder; if (!adminApp?.enabled) { return undefined; @@ -93,6 +93,10 @@ function buildAdminRoutes( ] : []), ], + requiredRoles: + adminApp.allowedRoles?.map((roleId) => + definitionContainer.nameFromId(roleId), + ) ?? [], }), admin: adminHomeGenerator({}), adminRoutes: backendApp.enableBullQueue diff --git a/packages/project-builder-web/src/components/embedded-list-input/embedded-list-input.tsx b/packages/project-builder-web/src/components/embedded-list-input/embedded-list-input.tsx index 9df4a5301..6baf5b5ee 100644 --- a/packages/project-builder-web/src/components/embedded-list-input/embedded-list-input.tsx +++ b/packages/project-builder-web/src/components/embedded-list-input/embedded-list-input.tsx @@ -9,6 +9,8 @@ import type { import { Alert, + AlertDescription, + AlertTitle, Button, Dialog, DialogContent, @@ -99,7 +101,10 @@ export function EmbeddedListInput({ }, }) ) : ( - No items currently + + No items currently + Add an item to get started. + )} { @@ -67,6 +66,10 @@ function NewAppDialog({ { ...data, id: newId, + ...(data.type === 'web' && { + title: startCase(data.name), + description: `A ${data.type} application`, + }), }, ]; draftConfig.apps = sortBy(newApps, [(app) => app.name]) as AppConfig[]; @@ -122,7 +125,7 @@ function NewAppDialog({ control={control} name="type" options={appTypeOptions} - description="Backend apps provide APIs, web apps are client applications, and admin apps manage data" + description="Backend apps provide APIs, web apps are client applications" /> + + + + } + /> + ); + }`, + ); + }, + }), + }), +}); diff --git a/packages/react-generators/src/generators/auth/auth-errors/extractor.json b/packages/react-generators/src/generators/auth/auth-errors/extractor.json new file mode 100644 index 000000000..857f4eeeb --- /dev/null +++ b/packages/react-generators/src/generators/auth/auth-errors/extractor.json @@ -0,0 +1,15 @@ +{ + "name": "auth/auth-errors", + "templates": { + "auth-errors": { + "type": "ts", + "fileOptions": { "kind": "singleton" }, + "group": "main", + "importMapProviders": {}, + "pathRootRelativePath": "{src-root}/utils/auth-errors.ts", + "projectExports": { "InvalidRoleError": {} }, + "sourceFile": "src/utils/auth-errors.ts", + "variables": {} + } + } +} diff --git a/packages/react-generators/src/generators/auth/auth-errors/generated/index.ts b/packages/react-generators/src/generators/auth/auth-errors/generated/index.ts new file mode 100644 index 000000000..57a261a39 --- /dev/null +++ b/packages/react-generators/src/generators/auth/auth-errors/generated/index.ts @@ -0,0 +1,11 @@ +import { AUTH_AUTH_ERRORS_PATHS } from './template-paths.js'; +import { AUTH_AUTH_ERRORS_RENDERERS } from './template-renderers.js'; +import { AUTH_AUTH_ERRORS_IMPORTS } from './ts-import-providers.js'; +import { AUTH_AUTH_ERRORS_TEMPLATES } from './typed-templates.js'; + +export const AUTH_AUTH_ERRORS_GENERATED = { + imports: AUTH_AUTH_ERRORS_IMPORTS, + paths: AUTH_AUTH_ERRORS_PATHS, + renderers: AUTH_AUTH_ERRORS_RENDERERS, + templates: AUTH_AUTH_ERRORS_TEMPLATES, +}; diff --git a/packages/react-generators/src/generators/auth/auth-errors/generated/template-paths.ts b/packages/react-generators/src/generators/auth/auth-errors/generated/template-paths.ts new file mode 100644 index 000000000..cd77169c7 --- /dev/null +++ b/packages/react-generators/src/generators/auth/auth-errors/generated/template-paths.ts @@ -0,0 +1,29 @@ +import { packageInfoProvider } from '@baseplate-dev/core-generators'; +import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; + +export interface AuthAuthErrorsPaths { + authErrors: string; +} + +const authAuthErrorsPaths = createProviderType( + 'auth-auth-errors-paths', +); + +const authAuthErrorsPathsTask = createGeneratorTask({ + dependencies: { packageInfo: packageInfoProvider }, + exports: { authAuthErrorsPaths: authAuthErrorsPaths.export() }, + run({ packageInfo }) { + const srcRoot = packageInfo.getPackageSrcPath(); + + return { + providers: { + authAuthErrorsPaths: { authErrors: `${srcRoot}/utils/auth-errors.ts` }, + }, + }; + }, +}); + +export const AUTH_AUTH_ERRORS_PATHS = { + provider: authAuthErrorsPaths, + task: authAuthErrorsPathsTask, +}; diff --git a/packages/react-generators/src/generators/auth/auth-errors/generated/template-renderers.ts b/packages/react-generators/src/generators/auth/auth-errors/generated/template-renderers.ts new file mode 100644 index 000000000..9b8cc50f5 --- /dev/null +++ b/packages/react-generators/src/generators/auth/auth-errors/generated/template-renderers.ts @@ -0,0 +1,54 @@ +import type { RenderTsTemplateGroupActionInput } from '@baseplate-dev/core-generators'; +import type { BuilderAction } from '@baseplate-dev/sync'; + +import { typescriptFileProvider } from '@baseplate-dev/core-generators'; +import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; + +import { AUTH_AUTH_ERRORS_PATHS } from './template-paths.js'; +import { AUTH_AUTH_ERRORS_TEMPLATES } from './typed-templates.js'; + +export interface AuthAuthErrorsRenderers { + mainGroup: { + render: ( + options: Omit< + RenderTsTemplateGroupActionInput< + typeof AUTH_AUTH_ERRORS_TEMPLATES.mainGroup + >, + 'importMapProviders' | 'group' | 'paths' | 'generatorPaths' + >, + ) => BuilderAction; + }; +} + +const authAuthErrorsRenderers = createProviderType( + 'auth-auth-errors-renderers', +); + +const authAuthErrorsRenderersTask = createGeneratorTask({ + dependencies: { + paths: AUTH_AUTH_ERRORS_PATHS.provider, + typescriptFile: typescriptFileProvider, + }, + exports: { authAuthErrorsRenderers: authAuthErrorsRenderers.export() }, + run({ paths, typescriptFile }) { + return { + providers: { + authAuthErrorsRenderers: { + mainGroup: { + render: (options) => + typescriptFile.renderTemplateGroup({ + group: AUTH_AUTH_ERRORS_TEMPLATES.mainGroup, + paths, + ...options, + }), + }, + }, + }, + }; + }, +}); + +export const AUTH_AUTH_ERRORS_RENDERERS = { + provider: authAuthErrorsRenderers, + task: authAuthErrorsRenderersTask, +}; diff --git a/packages/react-generators/src/generators/auth/auth-errors/generated/ts-import-providers.ts b/packages/react-generators/src/generators/auth/auth-errors/generated/ts-import-providers.ts new file mode 100644 index 000000000..d6011c826 --- /dev/null +++ b/packages/react-generators/src/generators/auth/auth-errors/generated/ts-import-providers.ts @@ -0,0 +1,46 @@ +import type { TsImportMapProviderFromSchema } from '@baseplate-dev/core-generators'; + +import { + createTsImportMap, + createTsImportMapSchema, + packageScope, +} from '@baseplate-dev/core-generators'; +import { + createGeneratorTask, + createReadOnlyProviderType, +} from '@baseplate-dev/sync'; + +import { AUTH_AUTH_ERRORS_PATHS } from './template-paths.js'; + +const authErrorsImportsSchema = createTsImportMapSchema({ + InvalidRoleError: {}, +}); + +export type AuthErrorsImportsProvider = TsImportMapProviderFromSchema< + typeof authErrorsImportsSchema +>; + +export const authErrorsImportsProvider = + createReadOnlyProviderType('auth-errors-imports'); + +const authAuthErrorsImportsTask = createGeneratorTask({ + dependencies: { + paths: AUTH_AUTH_ERRORS_PATHS.provider, + }, + exports: { + authErrorsImports: authErrorsImportsProvider.export(packageScope), + }, + run({ paths }) { + return { + providers: { + authErrorsImports: createTsImportMap(authErrorsImportsSchema, { + InvalidRoleError: paths.authErrors, + }), + }, + }; + }, +}); + +export const AUTH_AUTH_ERRORS_IMPORTS = { + task: authAuthErrorsImportsTask, +}; diff --git a/packages/react-generators/src/generators/auth/auth-errors/generated/typed-templates.ts b/packages/react-generators/src/generators/auth/auth-errors/generated/typed-templates.ts new file mode 100644 index 000000000..cf51562ae --- /dev/null +++ b/packages/react-generators/src/generators/auth/auth-errors/generated/typed-templates.ts @@ -0,0 +1,21 @@ +import { createTsTemplateFile } from '@baseplate-dev/core-generators'; +import path from 'node:path'; + +const authErrors = createTsTemplateFile({ + fileOptions: { kind: 'singleton' }, + group: 'main', + importMapProviders: {}, + name: 'auth-errors', + projectExports: { InvalidRoleError: {} }, + source: { + path: path.join( + import.meta.dirname, + '../templates/src/utils/auth-errors.ts', + ), + }, + variables: {}, +}); + +export const mainGroup = { authErrors }; + +export const AUTH_AUTH_ERRORS_TEMPLATES = { mainGroup }; diff --git a/packages/react-generators/src/generators/auth/auth-errors/index.ts b/packages/react-generators/src/generators/auth/auth-errors/index.ts new file mode 100644 index 000000000..074421baa --- /dev/null +++ b/packages/react-generators/src/generators/auth/auth-errors/index.ts @@ -0,0 +1,3 @@ +export * from './auth-errors.generator.js'; +export type { AuthErrorsImportsProvider } from './generated/ts-import-providers.js'; +export { authErrorsImportsProvider } from './generated/ts-import-providers.js'; diff --git a/packages/react-generators/src/generators/auth/auth-errors/templates/src/utils/auth-errors.ts b/packages/react-generators/src/generators/auth/auth-errors/templates/src/utils/auth-errors.ts new file mode 100644 index 000000000..9b4aec090 --- /dev/null +++ b/packages/react-generators/src/generators/auth/auth-errors/templates/src/utils/auth-errors.ts @@ -0,0 +1,8 @@ +// @ts-nocheck + +export class InvalidRoleError extends Error { + constructor(message: string) { + super(message); + this.name = 'InvalidRoleError'; + } +} diff --git a/packages/react-generators/src/generators/auth/auth-identify/auth-identify.generator.ts b/packages/react-generators/src/generators/auth/auth-identify/auth-identify.generator.ts index 5cc05fdfd..e7b419bf5 100644 --- a/packages/react-generators/src/generators/auth/auth-identify/auth-identify.generator.ts +++ b/packages/react-generators/src/generators/auth/auth-identify/auth-identify.generator.ts @@ -5,6 +5,7 @@ import { packageScope, TsCodeUtils, tsImportBuilder, + tsTemplate, } from '@baseplate-dev/core-generators'; import { createConfigFieldMap, @@ -17,7 +18,7 @@ import { z } from 'zod'; import { reactRouterConfigProvider } from '#src/generators/core/react-router/index.js'; -import { authContextTask } from '../_tasks/auth-context.js'; +import { authHooksImportsProvider } from '../_providers/auth-hooks.js'; const descriptorSchema = z.object({}); @@ -37,7 +38,36 @@ export const authIdentifyGenerator = createGenerator({ generatorFileUrl: import.meta.url, descriptorSchema, buildTasks: () => ({ - authContext: authContextTask, + authContext: createGeneratorTask({ + dependencies: { + reactRouterConfig: reactRouterConfigProvider, + authHooksImports: authHooksImportsProvider, + }, + run({ reactRouterConfig, authHooksImports }) { + reactRouterConfig.routerSetupFragments.set( + 'auth-context', + tsTemplate`const session = ${authHooksImports.useSession.fragment()}();\nconst { userId } = session;`, + ); + reactRouterConfig.rootContextFields.add({ + name: 'userId', + type: tsTemplate`string | undefined`, + optional: true, + routerProviderInitializer: { + code: tsTemplate`userId`, + dependencies: ['userId'], + }, + }); + reactRouterConfig.rootContextFields.add({ + name: 'session', + type: authHooksImports.SessionData.typeFragment(), + optional: false, + routerProviderInitializer: { + code: tsTemplate`session`, + dependencies: ['session'], + }, + }); + }, + }), main: createGeneratorTask({ dependencies: { reactRouterConfig: reactRouterConfigProvider, diff --git a/packages/react-generators/src/generators/auth/index.ts b/packages/react-generators/src/generators/auth/index.ts index e8ae6e8e7..ad8c02d08 100644 --- a/packages/react-generators/src/generators/auth/index.ts +++ b/packages/react-generators/src/generators/auth/index.ts @@ -1,3 +1,3 @@ export * from './_providers/index.js'; -export * from './_tasks/index.js'; +export * from './auth-errors/index.js'; export * from './auth-identify/index.js'; diff --git a/packages/react-generators/src/generators/core/react-app/react-app.generator.ts b/packages/react-generators/src/generators/core/react-app/react-app.generator.ts index f9f42442d..b5e1e5ea9 100644 --- a/packages/react-generators/src/generators/core/react-app/react-app.generator.ts +++ b/packages/react-generators/src/generators/core/react-app/react-app.generator.ts @@ -28,6 +28,7 @@ type CodeFragmentWrapper = (contents: TsCodeFragment) => TsCodeFragment; * * - `auth`: Wrapper for authentication * - `data`: Wrapper for data fetching + * - `router`: Wrapper for routing */ type RenderWrapperType = 'auth' | 'data' | 'router'; diff --git a/packages/react-generators/src/generators/core/react-components/react-components.generator.ts b/packages/react-generators/src/generators/core/react-components/react-components.generator.ts index b4e1fa290..04b275b68 100644 --- a/packages/react-generators/src/generators/core/react-components/react-components.generator.ts +++ b/packages/react-generators/src/generators/core/react-components/react-components.generator.ts @@ -39,7 +39,6 @@ export const reactComponentsGenerator = createGenerator({ buildTasks: () => ({ nodePackages: createNodePackagesTask({ prod: extractPackageVersions(REACT_PACKAGES, [ - '@headlessui/react', '@hookform/resolvers', 'clsx', 'react-hook-form', diff --git a/packages/react-generators/src/generators/core/react-router/extractor.json b/packages/react-generators/src/generators/core/react-router/extractor.json index 305c881ae..fc3ebea86 100644 --- a/packages/react-generators/src/generators/core/react-router/extractor.json +++ b/packages/react-generators/src/generators/core/react-router/extractor.json @@ -48,6 +48,8 @@ "TPL_ADDITIONAL_ROUTER_OPTIONS": {}, "TPL_COMPONENT_BODY": {}, "TPL_COMPONENT_SETUP": {}, + "TPL_ERROR_COMPONENT_BODY": {}, + "TPL_ERROR_COMPONENT_HEADER": {}, "TPL_ROUTER_CONTEXT": {}, "TPL_ROUTER_PROVIDER": {} } diff --git a/packages/react-generators/src/generators/core/react-router/generated/typed-templates.ts b/packages/react-generators/src/generators/core/react-router/generated/typed-templates.ts index a8fbf856a..d301fe9b8 100644 --- a/packages/react-generators/src/generators/core/react-router/generated/typed-templates.ts +++ b/packages/react-generators/src/generators/core/react-router/generated/typed-templates.ts @@ -46,6 +46,8 @@ const router = createTsTemplateFile({ TPL_ADDITIONAL_ROUTER_OPTIONS: {}, TPL_COMPONENT_BODY: {}, TPL_COMPONENT_SETUP: {}, + TPL_ERROR_COMPONENT_BODY: {}, + TPL_ERROR_COMPONENT_HEADER: {}, TPL_ROUTER_CONTEXT: {}, TPL_ROUTER_PROVIDER: {}, }, diff --git a/packages/react-generators/src/generators/core/react-router/react-router.generator.ts b/packages/react-generators/src/generators/core/react-router/react-router.generator.ts index 9bbd34b07..16fd4ba73 100644 --- a/packages/react-generators/src/generators/core/react-router/react-router.generator.ts +++ b/packages/react-generators/src/generators/core/react-router/react-router.generator.ts @@ -66,6 +66,10 @@ const [setupTask, reactRouterConfigProvider, reactRouterConfigValuesProvider] = rootLayoutComponent: t.scalar(), /* The fields in the root route context */ rootContextFields: t.namedArray(), + /* Any fragments in the ErrorComponent header */ + errorComponentHeaderFragments: t.map(), + /* Any fragments in the ErrorComponent body */ + errorComponentBodyFragments: t.map(), }), { prefix: 'react-router', @@ -168,6 +172,8 @@ export const reactRouterGenerator = createGenerator({ routerBodyFragments, rootLayoutComponent, rootContextFields, + errorComponentHeaderFragments, + errorComponentBodyFragments, }, renderers, reactErrorImports, @@ -232,6 +238,14 @@ export const reactRouterGenerator = createGenerator({ await builder.apply( renderers.router.render({ variables: { + TPL_ERROR_COMPONENT_HEADER: TsCodeUtils.mergeFragments( + errorComponentHeaderFragments, + '\n\n', + ), + TPL_ERROR_COMPONENT_BODY: TsCodeUtils.mergeFragments( + errorComponentBodyFragments, + '\n\n', + ), TPL_ADDITIONAL_ROUTER_OPTIONS: rootContextFields.length > 0 ? tsTemplate` diff --git a/packages/react-generators/src/generators/core/react-router/templates/src/app/router.tsx b/packages/react-generators/src/generators/core/react-router/templates/src/app/router.tsx index 19df9582c..1ec44fcbc 100644 --- a/packages/react-generators/src/generators/core/react-router/templates/src/app/router.tsx +++ b/packages/react-generators/src/generators/core/react-router/templates/src/app/router.tsx @@ -6,15 +6,21 @@ import { routeTree } from '$routeTree'; import { Button, ErrorDisplay, NotFoundCard } from '%reactComponentsImports'; import { createRouter } from '@tanstack/react-router'; -const ErrorComponent: ErrorRouteComponent = ({ +function ErrorComponent({ error, reset, -}: React.ComponentProps) => ( - Reset} - /> -); +}: React.ComponentProps): React.ReactElement { + TPL_ERROR_COMPONENT_HEADER; + + TPL_ERROR_COMPONENT_BODY; + + return ( + Reset} + /> + ); +} export const router = createRouter({ routeTree, diff --git a/plugins/plugin-auth/package.json b/plugins/plugin-auth/package.json index fc2fdf9e3..e913c8f28 100644 --- a/plugins/plugin-auth/package.json +++ b/plugins/plugin-auth/package.json @@ -47,6 +47,7 @@ "@baseplate-dev/fastify-generators": "workspace:*", "@baseplate-dev/react-generators": "workspace:*", "@baseplate-dev/ui-components": "workspace:*", + "@baseplate-dev/utils": "workspace:*", "@hookform/lenses": "0.7.1", "@hookform/resolvers": "5.0.1", "@tanstack/react-router": "1.124.0", diff --git a/plugins/plugin-auth/src/auth/core/node.ts b/plugins/plugin-auth/src/auth/core/node.ts index 45254d919..897d92282 100644 --- a/plugins/plugin-auth/src/auth/core/node.ts +++ b/plugins/plugin-auth/src/auth/core/node.ts @@ -12,7 +12,10 @@ import { PluginUtils, webAppEntryType, } from '@baseplate-dev/project-builder-lib'; -import { authIdentifyGenerator } from '@baseplate-dev/react-generators'; +import { + authErrorsGenerator, + authIdentifyGenerator, +} from '@baseplate-dev/react-generators'; import type { AuthPluginDefinition } from './schema/plugin-definition.js'; @@ -58,6 +61,7 @@ export default createPlatformPluginExport({ compile: ({ appCompiler }) => { appCompiler.addRootChildren({ authIdentify: authIdentifyGenerator({}), + authErrors: authErrorsGenerator({}), }); }, }); diff --git a/plugins/plugin-auth/src/auth0/core/node.ts b/plugins/plugin-auth/src/auth0/core/node.ts index 7ca2ec59f..634ac671e 100644 --- a/plugins/plugin-auth/src/auth0/core/node.ts +++ b/plugins/plugin-auth/src/auth0/core/node.ts @@ -52,10 +52,14 @@ export default createPlatformPluginExport({ appCompiler.registerAppCompiler({ pluginKey, appType: webAppEntryType, - compile: ({ appCompiler }) => { + compile: ({ appCompiler, projectDefinition }) => { + const auth = getAuthPluginDefinition(projectDefinition); + appCompiler.addRootChildren({ auth: reactAuth0Generator({}), - authHooks: auth0HooksGenerator({}), + authHooks: auth0HooksGenerator({ + authRoles: auth.roles.map((role) => role.name), + }), auth0Apollo: auth0ApolloGenerator({}), auth0Callback: reactRoutesGenerator({ name: 'auth', diff --git a/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/auth0-hooks.generator.ts b/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/auth0-hooks.generator.ts index 6fb3fe406..55bba69e6 100644 --- a/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/auth0-hooks.generator.ts +++ b/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/auth0-hooks.generator.ts @@ -1,17 +1,20 @@ +import { TsCodeUtils } from '@baseplate-dev/core-generators'; import { createGenerator, createGeneratorTask } from '@baseplate-dev/sync'; +import { quot } from '@baseplate-dev/utils'; import { z } from 'zod'; import { AUTH0_AUTH0_HOOKS_GENERATED } from './generated/index.js'; const descriptorSchema = z.object({ userQueryName: z.string().default('user'), + authRoles: z.array(z.string()).default([]), }); export const auth0HooksGenerator = createGenerator({ name: 'auth0/auth0-hooks', generatorFileUrl: import.meta.url, descriptorSchema, - buildTasks: ({ userQueryName }) => ({ + buildTasks: ({ userQueryName, authRoles }) => ({ paths: AUTH0_AUTH0_HOOKS_GENERATED.paths.task, imports: AUTH0_AUTH0_HOOKS_GENERATED.imports.task, renderers: AUTH0_AUTH0_HOOKS_GENERATED.renderers.task, @@ -28,6 +31,11 @@ export const auth0HooksGenerator = createGenerator({ useCurrentUser: { TPL_USER: userQueryName, }, + useSession: { + TPL_AUTH_ROLES: TsCodeUtils.mergeFragmentsAsArrayPresorted( + [...authRoles].sort().map((role) => quot(role)), + ), + }, }, }), ); diff --git a/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/extractor.json b/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/extractor.json index 7df55f80b..d1090cee8 100644 --- a/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/extractor.json +++ b/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/extractor.json @@ -71,11 +71,12 @@ "importMapProviders": {}, "pathRootRelativePath": "{src-root}/hooks/use-session.ts", "projectExports": { + "AuthRole": { "isTypeOnly": true }, "SessionData": { "isTypeOnly": true }, "useSession": {} }, "sourceFile": "src/hooks/use-session.ts", - "variables": {} + "variables": { "TPL_AUTH_ROLES": {} } } } } diff --git a/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/generated/ts-import-providers.ts b/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/generated/ts-import-providers.ts index 41b22be5c..86410ae75 100644 --- a/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/generated/ts-import-providers.ts +++ b/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/generated/ts-import-providers.ts @@ -19,6 +19,7 @@ const auth0Auth0HooksImportsTask = createGeneratorTask({ return { providers: { authHooksImports: createTsImportMap(authHooksImportsSchema, { + AuthRole: paths.useSession, SessionData: paths.useSession, useCurrentUser: paths.useCurrentUser, useLogOut: paths.useLogOut, diff --git a/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/generated/typed-templates.ts b/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/generated/typed-templates.ts index 5beaf9048..df8b2882e 100644 --- a/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/generated/typed-templates.ts +++ b/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/generated/typed-templates.ts @@ -62,14 +62,18 @@ const useSession = createTsTemplateFile({ group: 'hooks', importMapProviders: {}, name: 'use-session', - projectExports: { SessionData: { isTypeOnly: true }, useSession: {} }, + projectExports: { + AuthRole: { isTypeOnly: true }, + SessionData: { isTypeOnly: true }, + useSession: {}, + }, source: { path: path.join( import.meta.dirname, '../templates/src/hooks/use-session.ts', ), }, - variables: {}, + variables: { TPL_AUTH_ROLES: {} }, }); export const hooksGroup = { diff --git a/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/templates/src/hooks/use-session.ts b/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/templates/src/hooks/use-session.ts index 30200c54e..95f0a317f 100644 --- a/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/templates/src/hooks/use-session.ts +++ b/plugins/plugin-auth/src/auth0/generators/react/auth0-hooks/templates/src/hooks/use-session.ts @@ -3,21 +3,34 @@ import { useAuth0 } from '@auth0/auth0-react'; import { useMemo } from 'react'; +const AUTH_ROLES = TPL_AUTH_ROLES as const; +export type AuthRole = (typeof AUTH_ROLES)[number]; + export interface SessionData { userId: string | undefined; isAuthenticated: boolean; + roles: AuthRole[]; } + const USER_ID_CLAIM = 'https://app.com/user_id'; +const ROLES_CLAIM = 'https://app.com/roles'; export function useSession(): SessionData { const { user, isAuthenticated } = useAuth0(); + const rolesClaim = user?.[ROLES_CLAIM] as string[] | undefined; + const sessionData: SessionData = useMemo( () => ({ userId: user?.[USER_ID_CLAIM] as string | undefined, isAuthenticated, + roles: Array.isArray(rolesClaim) + ? rolesClaim.filter((role): role is AuthRole => + AUTH_ROLES.includes(role as AuthRole), + ) + : ['public'], }), - [user, isAuthenticated], + [user, isAuthenticated, rolesClaim], ); return sessionData; } diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-apollo/auth-apollo.generator.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-apollo/auth-apollo.generator.ts index 3a93b1027..084408b07 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-apollo/auth-apollo.generator.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-apollo/auth-apollo.generator.ts @@ -12,7 +12,7 @@ import { reactSessionImportsProvider } from '../react-session/index.js'; const descriptorSchema = z.object({}); export const authApolloGenerator = createGenerator({ - name: 'auth/core/auth-apollo', + name: 'local-auth/core/auth-apollo', generatorFileUrl: import.meta.url, descriptorSchema, buildTasks: () => ({ @@ -22,15 +22,6 @@ export const authApolloGenerator = createGenerator({ reactSession: reactSessionImportsProvider, }, run({ reactApolloConfig, reactSession }) { - reactApolloConfig.createApolloClientArguments.add({ - name: 'userSessionClient', - type: reactSession.UserSessionClient.typeFragment(), - reactRenderBody: tsCodeFragment( - 'const { client: userSessionClient } = useUserSessionClient();', - reactSession.useUserSessionClient.declaration(), - ), - }); - return { providers: { authApollo: {}, @@ -48,6 +39,7 @@ export const authApolloGenerator = createGenerator({ name: 'sessionErrorLink', bodyFragment: tsCodeFragment(sessionErrorLink, [ tsImportBuilder(['onError']).from('@apollo/client/link/error'), + reactSession.userSessionClient.declaration(), ]), priority: 'error', }); diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/auth-email-password.generator.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/auth-email-password.generator.ts index eb47a3743..ae737c75f 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/auth-email-password.generator.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/auth-email-password.generator.ts @@ -2,7 +2,7 @@ import { appModuleProvider } from '@baseplate-dev/fastify-generators'; import { createGenerator, createGeneratorTask } from '@baseplate-dev/sync'; import { z } from 'zod'; -import { AUTH_CORE_AUTH_EMAIL_PASSWORD_GENERATED as GENERATED_TEMPLATES } from './generated/index.js'; +import { LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_GENERATED as GENERATED_TEMPLATES } from './generated/index.js'; const descriptorSchema = z.object({}); @@ -10,7 +10,7 @@ const descriptorSchema = z.object({}); * Sets up email / password authentication */ export const authEmailPasswordGenerator = createGenerator({ - name: 'auth/core/auth-email-password', + name: 'local-auth/core/auth-email-password', generatorFileUrl: import.meta.url, descriptorSchema, buildTasks: () => ({ diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/extractor.json b/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/extractor.json index 39435f373..f2c5e5a11 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/extractor.json +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/extractor.json @@ -1,10 +1,10 @@ { - "name": "auth/core/auth-email-password", + "name": "local-auth/core/auth-email-password", "templates": { "constants-password": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-email-password", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-email-password", "group": "module", "importMapProviders": {}, "pathRootRelativePath": "{module-root}/constants/password.constants.ts", @@ -15,12 +15,12 @@ "schema-user-password-mutations": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-email-password", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-email-password", "group": "module", "importMapProviders": { "authModuleImportsProvider": { "importName": "authModuleImportsProvider", - "packagePathSpecifier": "@baseplate-dev/plugin-auth:src/auth/core/generators/auth-module/generated/ts-import-providers.ts" + "packagePathSpecifier": "@baseplate-dev/plugin-auth:src/local-auth/core/generators/auth-module/generated/ts-import-providers.ts" }, "pothosImportsProvider": { "importName": "pothosImportsProvider", @@ -35,7 +35,7 @@ "services-user-password": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-email-password", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-email-password", "group": "module", "importMapProviders": { "errorHandlerServiceImportsProvider": { diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/index.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/index.ts index 855ca08de..c6ed6240a 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/index.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/index.ts @@ -1,11 +1,11 @@ -import { AUTH_CORE_AUTH_EMAIL_PASSWORD_PATHS } from './template-paths.js'; -import { AUTH_CORE_AUTH_EMAIL_PASSWORD_RENDERERS } from './template-renderers.js'; -import { AUTH_CORE_AUTH_EMAIL_PASSWORD_IMPORTS } from './ts-import-providers.js'; -import { AUTH_CORE_AUTH_EMAIL_PASSWORD_TEMPLATES } from './typed-templates.js'; +import { LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_PATHS } from './template-paths.js'; +import { LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_RENDERERS } from './template-renderers.js'; +import { LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_IMPORTS } from './ts-import-providers.js'; +import { LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_TEMPLATES } from './typed-templates.js'; -export const AUTH_CORE_AUTH_EMAIL_PASSWORD_GENERATED = { - imports: AUTH_CORE_AUTH_EMAIL_PASSWORD_IMPORTS, - paths: AUTH_CORE_AUTH_EMAIL_PASSWORD_PATHS, - renderers: AUTH_CORE_AUTH_EMAIL_PASSWORD_RENDERERS, - templates: AUTH_CORE_AUTH_EMAIL_PASSWORD_TEMPLATES, +export const LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_GENERATED = { + imports: LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_IMPORTS, + paths: LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_PATHS, + renderers: LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_RENDERERS, + templates: LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_TEMPLATES, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/template-paths.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/template-paths.ts index 825bc7f05..3afebeb18 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/template-paths.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/template-paths.ts @@ -1,28 +1,29 @@ import { appModuleProvider } from '@baseplate-dev/fastify-generators'; import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; -export interface AuthCoreAuthEmailPasswordPaths { +export interface LocalAuthCoreAuthEmailPasswordPaths { constantsPassword: string; schemaUserPasswordMutations: string; servicesUserPassword: string; } -const authCoreAuthEmailPasswordPaths = - createProviderType( - 'auth-core-auth-email-password-paths', +const localAuthCoreAuthEmailPasswordPaths = + createProviderType( + 'local-auth-core-auth-email-password-paths', ); -const authCoreAuthEmailPasswordPathsTask = createGeneratorTask({ +const localAuthCoreAuthEmailPasswordPathsTask = createGeneratorTask({ dependencies: { appModule: appModuleProvider }, exports: { - authCoreAuthEmailPasswordPaths: authCoreAuthEmailPasswordPaths.export(), + localAuthCoreAuthEmailPasswordPaths: + localAuthCoreAuthEmailPasswordPaths.export(), }, run({ appModule }) { const moduleRoot = appModule.getModuleFolder(); return { providers: { - authCoreAuthEmailPasswordPaths: { + localAuthCoreAuthEmailPasswordPaths: { constantsPassword: `${moduleRoot}/constants/password.constants.ts`, schemaUserPasswordMutations: `${moduleRoot}/schema/user-password.mutations.ts`, servicesUserPassword: `${moduleRoot}/services/user-password.service.ts`, @@ -32,7 +33,7 @@ const authCoreAuthEmailPasswordPathsTask = createGeneratorTask({ }, }); -export const AUTH_CORE_AUTH_EMAIL_PASSWORD_PATHS = { - provider: authCoreAuthEmailPasswordPaths, - task: authCoreAuthEmailPasswordPathsTask, +export const LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_PATHS = { + provider: localAuthCoreAuthEmailPasswordPaths, + task: localAuthCoreAuthEmailPasswordPathsTask, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/template-renderers.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/template-renderers.ts index baa01ec21..18f658d15 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/template-renderers.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/template-renderers.ts @@ -15,15 +15,15 @@ import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; import { authModuleImportsProvider } from '#src/local-auth/core/generators/auth-module/generated/ts-import-providers.js'; -import { AUTH_CORE_AUTH_EMAIL_PASSWORD_PATHS } from './template-paths.js'; -import { AUTH_CORE_AUTH_EMAIL_PASSWORD_TEMPLATES } from './typed-templates.js'; +import { LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_PATHS } from './template-paths.js'; +import { LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_TEMPLATES } from './typed-templates.js'; -export interface AuthCoreAuthEmailPasswordRenderers { +export interface LocalAuthCoreAuthEmailPasswordRenderers { moduleGroup: { render: ( options: Omit< RenderTsTemplateGroupActionInput< - typeof AUTH_CORE_AUTH_EMAIL_PASSWORD_TEMPLATES.moduleGroup + typeof LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_TEMPLATES.moduleGroup >, 'importMapProviders' | 'group' | 'paths' | 'generatorPaths' >, @@ -31,17 +31,17 @@ export interface AuthCoreAuthEmailPasswordRenderers { }; } -const authCoreAuthEmailPasswordRenderers = - createProviderType( - 'auth-core-auth-email-password-renderers', +const localAuthCoreAuthEmailPasswordRenderers = + createProviderType( + 'local-auth-core-auth-email-password-renderers', ); -const authCoreAuthEmailPasswordRenderersTask = createGeneratorTask({ +const localAuthCoreAuthEmailPasswordRenderersTask = createGeneratorTask({ dependencies: { authModuleImports: authModuleImportsProvider, errorHandlerServiceImports: errorHandlerServiceImportsProvider, passwordHasherServiceImports: passwordHasherServiceImportsProvider, - paths: AUTH_CORE_AUTH_EMAIL_PASSWORD_PATHS.provider, + paths: LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_PATHS.provider, pothosImports: pothosImportsProvider, prismaImports: prismaImportsProvider, requestServiceContextImports: requestServiceContextImportsProvider, @@ -50,8 +50,8 @@ const authCoreAuthEmailPasswordRenderersTask = createGeneratorTask({ userSessionTypesImports: userSessionTypesImportsProvider, }, exports: { - authCoreAuthEmailPasswordRenderers: - authCoreAuthEmailPasswordRenderers.export(), + localAuthCoreAuthEmailPasswordRenderers: + localAuthCoreAuthEmailPasswordRenderers.export(), }, run({ authModuleImports, @@ -67,11 +67,12 @@ const authCoreAuthEmailPasswordRenderersTask = createGeneratorTask({ }) { return { providers: { - authCoreAuthEmailPasswordRenderers: { + localAuthCoreAuthEmailPasswordRenderers: { moduleGroup: { render: (options) => typescriptFile.renderTemplateGroup({ - group: AUTH_CORE_AUTH_EMAIL_PASSWORD_TEMPLATES.moduleGroup, + group: + LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_TEMPLATES.moduleGroup, paths, importMapProviders: { authModuleImports, @@ -93,7 +94,7 @@ const authCoreAuthEmailPasswordRenderersTask = createGeneratorTask({ }, }); -export const AUTH_CORE_AUTH_EMAIL_PASSWORD_RENDERERS = { - provider: authCoreAuthEmailPasswordRenderers, - task: authCoreAuthEmailPasswordRenderersTask, +export const LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_RENDERERS = { + provider: localAuthCoreAuthEmailPasswordRenderers, + task: localAuthCoreAuthEmailPasswordRenderersTask, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/ts-import-providers.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/ts-import-providers.ts index 14056b4a1..94803cb62 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/ts-import-providers.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/ts-import-providers.ts @@ -10,7 +10,7 @@ import { createReadOnlyProviderType, } from '@baseplate-dev/sync'; -import { AUTH_CORE_AUTH_EMAIL_PASSWORD_PATHS } from './template-paths.js'; +import { LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_PATHS } from './template-paths.js'; const authEmailPasswordImportsSchema = createTsImportMapSchema({ authenticateUserWithEmailAndPassword: {}, @@ -27,9 +27,9 @@ export const authEmailPasswordImportsProvider = 'auth-email-password-imports', ); -const authCoreAuthEmailPasswordImportsTask = createGeneratorTask({ +const localAuthCoreAuthEmailPasswordImportsTask = createGeneratorTask({ dependencies: { - paths: AUTH_CORE_AUTH_EMAIL_PASSWORD_PATHS.provider, + paths: LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_PATHS.provider, }, exports: { authEmailPasswordImports: @@ -51,6 +51,6 @@ const authCoreAuthEmailPasswordImportsTask = createGeneratorTask({ }, }); -export const AUTH_CORE_AUTH_EMAIL_PASSWORD_IMPORTS = { - task: authCoreAuthEmailPasswordImportsTask, +export const LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_IMPORTS = { + task: localAuthCoreAuthEmailPasswordImportsTask, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/typed-templates.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/typed-templates.ts index 2b79bcc16..f0f4bc7f9 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/typed-templates.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-email-password/generated/typed-templates.ts @@ -77,4 +77,4 @@ export const moduleGroup = { servicesUserPassword, }; -export const AUTH_CORE_AUTH_EMAIL_PASSWORD_TEMPLATES = { moduleGroup }; +export const LOCAL_AUTH_CORE_AUTH_EMAIL_PASSWORD_TEMPLATES = { moduleGroup }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/auth-hooks.generator.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/auth-hooks.generator.ts index 9149f2f1e..b3b286795 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/auth-hooks.generator.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/auth-hooks.generator.ts @@ -1,7 +1,7 @@ import { createGenerator, createGeneratorTask } from '@baseplate-dev/sync'; import { z } from 'zod'; -import { AUTH_CORE_AUTH_HOOKS_GENERATED as GENERATED_TEMPLATES } from './generated/index.js'; +import { LOCAL_AUTH_CORE_AUTH_HOOKS_GENERATED as GENERATED_TEMPLATES } from './generated/index.js'; const descriptorSchema = z.object({}); @@ -11,7 +11,7 @@ const descriptorSchema = z.object({}); * Useful for creating a test auth implementation. */ export const authHooksGenerator = createGenerator({ - name: 'auth/core/auth-hooks', + name: 'local-auth/core/auth-hooks', generatorFileUrl: import.meta.url, descriptorSchema, buildTasks: () => ({ diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/extractor.json b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/extractor.json index 91b431920..064521eb0 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/extractor.json +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/extractor.json @@ -1,18 +1,18 @@ { - "name": "auth/core/auth-hooks", + "name": "local-auth/core/auth-hooks", "extractors": { "ts": { + "defaultImportProviderName": "localAuthHooksImportsProvider", "importProviders": [ "@baseplate-dev/react-generators:authHooksImportsProvider" - ], - "skipDefaultImportMap": true + ] } }, "templates": { "use-current-user": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-hooks", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-hooks", "group": "hooks", "importMapProviders": { "generatedGraphqlImportsProvider": { @@ -37,7 +37,7 @@ "use-log-out": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-hooks", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-hooks", "group": "hooks", "importMapProviders": { "generatedGraphqlImportsProvider": { @@ -50,7 +50,7 @@ }, "reactSessionImportsProvider": { "importName": "reactSessionImportsProvider", - "packagePathSpecifier": "@baseplate-dev/plugin-auth:src/auth/core/generators/react-session/generated/ts-import-providers.ts" + "packagePathSpecifier": "@baseplate-dev/plugin-auth:src/local-auth/core/generators/react-session/generated/ts-import-providers.ts" } }, "pathRootRelativePath": "{src-root}/hooks/use-log-out.ts", @@ -69,7 +69,7 @@ "use-required-user-id": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-hooks", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-hooks", "group": "hooks", "importMapProviders": {}, "pathRootRelativePath": "{src-root}/hooks/use-user-id-or-throw.ts", @@ -81,16 +81,18 @@ "use-session": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-hooks", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-hooks", "group": "hooks", "importMapProviders": { - "reactSessionImportsProvider": { - "importName": "reactSessionImportsProvider", - "packagePathSpecifier": "@baseplate-dev/plugin-auth:src/auth/core/generators/react-session/generated/ts-import-providers.ts" + "generatedGraphqlImportsProvider": { + "importName": "generatedGraphqlImportsProvider", + "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/apollo/react-apollo/providers/generated-graphql.ts" } }, "pathRootRelativePath": "{src-root}/hooks/use-session.ts", "projectExports": { + "AuthRole": {}, + "AuthSessionContext": {}, "SessionData": { "isTypeOnly": true }, "useSession": {} }, diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/index.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/index.ts index 0af3e8f97..5f96aceb8 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/index.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/index.ts @@ -1,11 +1,11 @@ -import { AUTH_CORE_AUTH_HOOKS_PATHS } from './template-paths.js'; -import { AUTH_CORE_AUTH_HOOKS_RENDERERS } from './template-renderers.js'; -import { AUTH_CORE_AUTH_HOOKS_IMPORTS } from './ts-import-providers.js'; -import { AUTH_CORE_AUTH_HOOKS_TEMPLATES } from './typed-templates.js'; +import { LOCAL_AUTH_CORE_AUTH_HOOKS_PATHS } from './template-paths.js'; +import { LOCAL_AUTH_CORE_AUTH_HOOKS_RENDERERS } from './template-renderers.js'; +import { LOCAL_AUTH_CORE_AUTH_HOOKS_IMPORTS } from './ts-import-providers.js'; +import { LOCAL_AUTH_CORE_AUTH_HOOKS_TEMPLATES } from './typed-templates.js'; -export const AUTH_CORE_AUTH_HOOKS_GENERATED = { - imports: AUTH_CORE_AUTH_HOOKS_IMPORTS, - paths: AUTH_CORE_AUTH_HOOKS_PATHS, - renderers: AUTH_CORE_AUTH_HOOKS_RENDERERS, - templates: AUTH_CORE_AUTH_HOOKS_TEMPLATES, +export const LOCAL_AUTH_CORE_AUTH_HOOKS_GENERATED = { + imports: LOCAL_AUTH_CORE_AUTH_HOOKS_IMPORTS, + paths: LOCAL_AUTH_CORE_AUTH_HOOKS_PATHS, + renderers: LOCAL_AUTH_CORE_AUTH_HOOKS_RENDERERS, + templates: LOCAL_AUTH_CORE_AUTH_HOOKS_TEMPLATES, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/template-paths.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/template-paths.ts index 14a552586..ee28ea41b 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/template-paths.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/template-paths.ts @@ -1,7 +1,7 @@ import { packageInfoProvider } from '@baseplate-dev/core-generators'; import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; -export interface AuthCoreAuthHooksPaths { +export interface LocalAuthCoreAuthHooksPaths { useCurrentUser: string; useCurrentUserGql: string; useLogOut: string; @@ -10,19 +10,22 @@ export interface AuthCoreAuthHooksPaths { useSession: string; } -const authCoreAuthHooksPaths = createProviderType( - 'auth-core-auth-hooks-paths', -); +const localAuthCoreAuthHooksPaths = + createProviderType( + 'local-auth-core-auth-hooks-paths', + ); -const authCoreAuthHooksPathsTask = createGeneratorTask({ +const localAuthCoreAuthHooksPathsTask = createGeneratorTask({ dependencies: { packageInfo: packageInfoProvider }, - exports: { authCoreAuthHooksPaths: authCoreAuthHooksPaths.export() }, + exports: { + localAuthCoreAuthHooksPaths: localAuthCoreAuthHooksPaths.export(), + }, run({ packageInfo }) { const srcRoot = packageInfo.getPackageSrcPath(); return { providers: { - authCoreAuthHooksPaths: { + localAuthCoreAuthHooksPaths: { useCurrentUser: `${srcRoot}/hooks/use-current-user.ts`, useCurrentUserGql: `${srcRoot}/hooks/use-current-user.gql`, useLogOut: `${srcRoot}/hooks/use-log-out.ts`, @@ -35,7 +38,7 @@ const authCoreAuthHooksPathsTask = createGeneratorTask({ }, }); -export const AUTH_CORE_AUTH_HOOKS_PATHS = { - provider: authCoreAuthHooksPaths, - task: authCoreAuthHooksPathsTask, +export const LOCAL_AUTH_CORE_AUTH_HOOKS_PATHS = { + provider: localAuthCoreAuthHooksPaths, + task: localAuthCoreAuthHooksPathsTask, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/template-renderers.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/template-renderers.ts index 459712102..1d051e067 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/template-renderers.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/template-renderers.ts @@ -16,15 +16,15 @@ import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; import { reactSessionImportsProvider } from '#src/local-auth/core/generators/react-session/generated/ts-import-providers.js'; -import { AUTH_CORE_AUTH_HOOKS_PATHS } from './template-paths.js'; -import { AUTH_CORE_AUTH_HOOKS_TEMPLATES } from './typed-templates.js'; +import { LOCAL_AUTH_CORE_AUTH_HOOKS_PATHS } from './template-paths.js'; +import { LOCAL_AUTH_CORE_AUTH_HOOKS_TEMPLATES } from './typed-templates.js'; -export interface AuthCoreAuthHooksRenderers { +export interface LocalAuthCoreAuthHooksRenderers { hooksGqlGroup: { render: ( options: Omit< RenderTextTemplateGroupActionInput< - typeof AUTH_CORE_AUTH_HOOKS_TEMPLATES.hooksGqlGroup + typeof LOCAL_AUTH_CORE_AUTH_HOOKS_TEMPLATES.hooksGqlGroup >, 'group' | 'paths' >, @@ -34,7 +34,7 @@ export interface AuthCoreAuthHooksRenderers { render: ( options: Omit< RenderTsTemplateGroupActionInput< - typeof AUTH_CORE_AUTH_HOOKS_TEMPLATES.hooksGroup + typeof LOCAL_AUTH_CORE_AUTH_HOOKS_TEMPLATES.hooksGroup >, 'importMapProviders' | 'group' | 'paths' | 'generatorPaths' >, @@ -42,20 +42,22 @@ export interface AuthCoreAuthHooksRenderers { }; } -const authCoreAuthHooksRenderers = - createProviderType( - 'auth-core-auth-hooks-renderers', +const localAuthCoreAuthHooksRenderers = + createProviderType( + 'local-auth-core-auth-hooks-renderers', ); -const authCoreAuthHooksRenderersTask = createGeneratorTask({ +const localAuthCoreAuthHooksRenderersTask = createGeneratorTask({ dependencies: { generatedGraphqlImports: generatedGraphqlImportsProvider, - paths: AUTH_CORE_AUTH_HOOKS_PATHS.provider, + paths: LOCAL_AUTH_CORE_AUTH_HOOKS_PATHS.provider, reactErrorImports: reactErrorImportsProvider, reactSessionImports: reactSessionImportsProvider, typescriptFile: typescriptFileProvider, }, - exports: { authCoreAuthHooksRenderers: authCoreAuthHooksRenderers.export() }, + exports: { + localAuthCoreAuthHooksRenderers: localAuthCoreAuthHooksRenderers.export(), + }, run({ generatedGraphqlImports, paths, @@ -65,11 +67,11 @@ const authCoreAuthHooksRenderersTask = createGeneratorTask({ }) { return { providers: { - authCoreAuthHooksRenderers: { + localAuthCoreAuthHooksRenderers: { hooksGqlGroup: { render: (options) => renderTextTemplateGroupAction({ - group: AUTH_CORE_AUTH_HOOKS_TEMPLATES.hooksGqlGroup, + group: LOCAL_AUTH_CORE_AUTH_HOOKS_TEMPLATES.hooksGqlGroup, paths, ...options, }), @@ -77,7 +79,7 @@ const authCoreAuthHooksRenderersTask = createGeneratorTask({ hooksGroup: { render: (options) => typescriptFile.renderTemplateGroup({ - group: AUTH_CORE_AUTH_HOOKS_TEMPLATES.hooksGroup, + group: LOCAL_AUTH_CORE_AUTH_HOOKS_TEMPLATES.hooksGroup, paths, importMapProviders: { generatedGraphqlImports, @@ -94,7 +96,7 @@ const authCoreAuthHooksRenderersTask = createGeneratorTask({ }, }); -export const AUTH_CORE_AUTH_HOOKS_RENDERERS = { - provider: authCoreAuthHooksRenderers, - task: authCoreAuthHooksRenderersTask, +export const LOCAL_AUTH_CORE_AUTH_HOOKS_RENDERERS = { + provider: localAuthCoreAuthHooksRenderers, + task: localAuthCoreAuthHooksRenderersTask, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/ts-import-providers.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/ts-import-providers.ts index e280d23bb..0171b04fe 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/ts-import-providers.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/ts-import-providers.ts @@ -1,35 +1,61 @@ +import type { TsImportMapProviderFromSchema } from '@baseplate-dev/core-generators'; + import { createTsImportMap, + createTsImportMapSchema, packageScope, } from '@baseplate-dev/core-generators'; import { authHooksImportsProvider, authHooksImportsSchema, } from '@baseplate-dev/react-generators'; -import { createGeneratorTask } from '@baseplate-dev/sync'; +import { + createGeneratorTask, + createReadOnlyProviderType, +} from '@baseplate-dev/sync'; + +import { LOCAL_AUTH_CORE_AUTH_HOOKS_PATHS } from './template-paths.js'; + +const localAuthHooksImportsSchema = createTsImportMapSchema({ + AuthSessionContext: {}, +}); + +export type LocalAuthHooksImportsProvider = TsImportMapProviderFromSchema< + typeof localAuthHooksImportsSchema +>; -import { AUTH_CORE_AUTH_HOOKS_PATHS } from './template-paths.js'; +export const localAuthHooksImportsProvider = + createReadOnlyProviderType( + 'local-auth-hooks-imports', + ); -const authCoreAuthHooksImportsTask = createGeneratorTask({ +const localAuthCoreAuthHooksImportsTask = createGeneratorTask({ dependencies: { - paths: AUTH_CORE_AUTH_HOOKS_PATHS.provider, + paths: LOCAL_AUTH_CORE_AUTH_HOOKS_PATHS.provider, + }, + exports: { + authHooksImports: authHooksImportsProvider.export(packageScope), + localAuthHooksImports: localAuthHooksImportsProvider.export(packageScope), }, - exports: { authHooksImports: authHooksImportsProvider.export(packageScope) }, run({ paths }) { return { providers: { authHooksImports: createTsImportMap(authHooksImportsSchema, { + AuthRole: paths.useSession, SessionData: paths.useSession, useCurrentUser: paths.useCurrentUser, useLogOut: paths.useLogOut, useRequiredUserId: paths.useRequiredUserId, useSession: paths.useSession, }), + localAuthHooksImports: createTsImportMap(localAuthHooksImportsSchema, { + AuthSessionContext: paths.useSession, + }), }, }; }, }); -export const AUTH_CORE_AUTH_HOOKS_IMPORTS = { - task: authCoreAuthHooksImportsTask, +export const LOCAL_AUTH_CORE_AUTH_HOOKS_IMPORTS = { + task: localAuthCoreAuthHooksImportsTask, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/typed-templates.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/typed-templates.ts index 094d45dc8..1a69d0ae5 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/typed-templates.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/generated/typed-templates.ts @@ -94,9 +94,16 @@ const useRequiredUserId = createTsTemplateFile({ const useSession = createTsTemplateFile({ fileOptions: { kind: 'singleton' }, group: 'hooks', - importMapProviders: { reactSessionImports: reactSessionImportsProvider }, + importMapProviders: { + generatedGraphqlImports: generatedGraphqlImportsProvider, + }, name: 'use-session', - projectExports: { SessionData: { isTypeOnly: true }, useSession: {} }, + projectExports: { + AuthRole: {}, + AuthSessionContext: {}, + SessionData: { isTypeOnly: true }, + useSession: {}, + }, source: { path: path.join( import.meta.dirname, @@ -113,4 +120,7 @@ export const hooksGroup = { useSession, }; -export const AUTH_CORE_AUTH_HOOKS_TEMPLATES = { hooksGqlGroup, hooksGroup }; +export const LOCAL_AUTH_CORE_AUTH_HOOKS_TEMPLATES = { + hooksGqlGroup, + hooksGroup, +}; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/index.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/index.ts index a29d229b6..6bd3b330d 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/index.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/index.ts @@ -1 +1,3 @@ export * from './auth-hooks.generator.js'; +export type { LocalAuthHooksImportsProvider } from './generated/ts-import-providers.js'; +export { localAuthHooksImportsProvider } from './generated/ts-import-providers.js'; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/templates/src/hooks/use-log-out.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/templates/src/hooks/use-log-out.ts index f55f90b14..4a475e479 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/templates/src/hooks/use-log-out.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/templates/src/hooks/use-log-out.ts @@ -2,21 +2,20 @@ import { LogOutDocument } from '%generatedGraphqlImports'; import { logAndFormatError, logError } from '%reactErrorImports'; -import { useUserSessionClient } from '%reactSessionImports'; +import { userSessionClient } from '%reactSessionImports'; import { useApolloClient, useMutation } from '@apollo/client'; import { useNavigate } from '@tanstack/react-router'; import { toast } from 'sonner'; export function useLogOut(): () => void { const [logOut] = useMutation(LogOutDocument); - const { client } = useUserSessionClient(); const apolloClient = useApolloClient(); const navigate = useNavigate(); return () => { logOut() .then(() => { - client.signOut(); + userSessionClient.signOut(); // Make sure to reset the Apollo client to clear any cached data apolloClient.clearStore().catch(logError); toast.success('You have been successfully logged out!'); diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/templates/src/hooks/use-session.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/templates/src/hooks/use-session.ts index 444679534..811408dbb 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/templates/src/hooks/use-session.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-hooks/templates/src/hooks/use-session.ts @@ -1,27 +1,32 @@ // @ts-nocheck -import { useUserSessionClient } from '%reactSessionImports'; +import type { AuthRole } from '%generatedGraphqlImports'; + +import { createContext, useContext } from 'react'; + +export { type AuthRole } from '@src/generated/graphql'; export interface SessionData { userId: string | undefined; isAuthenticated: boolean; + roles: AuthRole[]; } +export const AuthSessionContext = createContext( + undefined, +); + /** * Provides the current session data such as the user id and whether the user is authenticated * This is the primary hook for accessing authentication state * @returns Current session data with computed isAuthenticated */ export function useSession(): SessionData { - const { session } = useUserSessionClient(); - - return session - ? { - userId: session.userId, - isAuthenticated: true, - } - : { - userId: undefined, - isAuthenticated: false, - }; + const contextValue = useContext(AuthSessionContext); + + if (!contextValue) { + throw new Error('useSession must be used within a AuthSessionProvider'); + } + + return contextValue; } diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/auth-module.generator.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/auth-module.generator.ts index de60da9b7..d07b89757 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/auth-module.generator.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/auth-module.generator.ts @@ -13,7 +13,7 @@ import { } from '@baseplate-dev/sync'; import { z } from 'zod'; -import { AUTH_CORE_AUTH_MODULE_GENERATED as GENERATED_TEMPLATES } from './generated/index.js'; +import { LOCAL_AUTH_CORE_AUTH_MODULE_GENERATED as GENERATED_TEMPLATES } from './generated/index.js'; const descriptorSchema = z.object({ userSessionModelName: z.string().min(1), @@ -21,7 +21,7 @@ const descriptorSchema = z.object({ }); export const authModuleGenerator = createGenerator({ - name: 'auth/core/auth-module', + name: 'local-auth/core/auth-module', generatorFileUrl: import.meta.url, descriptorSchema, buildTasks: ({ userSessionModelName, userModelName }) => ({ @@ -49,6 +49,7 @@ export const authModuleGenerator = createGenerator({ paths.schemaUserSessionMutations, paths.schemaUserSessionQueries, paths.schemaUserSessionPayloadObjectType, + paths.authRoleEnum, ); }, }), diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/extractor.json b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/extractor.json index f7a91d112..707d65608 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/extractor.json +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/extractor.json @@ -1,5 +1,5 @@ { - "name": "auth/core/auth-module", + "name": "local-auth/core/auth-module", "extractors": { "ts": { "importProviders": [ @@ -8,10 +8,29 @@ } }, "templates": { + "auth-role-enum": { + "type": "ts", + "fileOptions": { "kind": "singleton" }, + "group": "module", + "importMapProviders": { + "authRolesImportsProvider": { + "importName": "authRolesImportsProvider", + "packagePathSpecifier": "@baseplate-dev/fastify-generators:src/generators/auth/auth-roles/generated/ts-import-providers.ts" + }, + "pothosImportsProvider": { + "importName": "pothosImportsProvider", + "packagePathSpecifier": "@baseplate-dev/fastify-generators:src/generators/pothos/pothos/generated/ts-import-providers.ts" + } + }, + "pathRootRelativePath": "{module-root}/schema/auth-role.enum.ts", + "projectExports": {}, + "sourceFile": "module/schema/auth-role.enum.ts", + "variables": {} + }, "cookie-signer": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-module", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-module", "group": "utils", "importMapProviders": {}, "pathRootRelativePath": "{module-root}/utils/cookie-signer.ts", @@ -21,7 +40,7 @@ "schema-user-session-mutations": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-module", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-module", "group": "module", "importMapProviders": { "pothosImportsProvider": { @@ -37,7 +56,7 @@ "schema-user-session-payload-object-type": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-module", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-module", "group": "module", "importMapProviders": { "pothosImportsProvider": { @@ -47,13 +66,14 @@ }, "pathRootRelativePath": "{module-root}/schema/user-session-payload.object-type.ts", "projectExports": { "userSessionPayload": {} }, + "referencedGeneratorTemplates": ["auth-role-enum"], "sourceFile": "module/schema/user-session-payload.object-type.ts", "variables": { "TPL_PRISMA_USER": {}, "TPL_USER_OBJECT_TYPE": {} } }, "schema-user-session-queries": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-module", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-module", "group": "module", "importMapProviders": { "pothosImportsProvider": { @@ -71,7 +91,7 @@ "session-cookie": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-module", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-module", "group": "utils", "importMapProviders": { "configServiceImportsProvider": { @@ -86,7 +106,7 @@ "user-session-constants": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-module", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-module", "group": "constants", "importMapProviders": {}, "pathRootRelativePath": "{module-root}/constants/user-session.constants.ts", @@ -96,7 +116,7 @@ "user-session-service": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-module", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-module", "importMapProviders": { "authContextImportsProvider": { "importName": "authContextImportsProvider", @@ -114,6 +134,10 @@ "importName": "errorHandlerServiceImportsProvider", "packagePathSpecifier": "@baseplate-dev/fastify-generators:src/generators/core/error-handler-service/generated/ts-import-providers.ts" }, + "prismaImportsProvider": { + "importName": "prismaImportsProvider", + "packagePathSpecifier": "@baseplate-dev/fastify-generators:src/generators/prisma/prisma/generated/ts-import-providers.ts" + }, "requestServiceContextImportsProvider": { "importName": "requestServiceContextImportsProvider", "packagePathSpecifier": "@baseplate-dev/fastify-generators:src/generators/core/request-service-context/generated/ts-import-providers.ts" @@ -137,7 +161,7 @@ "verify-request-origin": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-module", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-module", "group": "utils", "importMapProviders": {}, "pathRootRelativePath": "{module-root}/utils/verify-request-origin.ts", diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/index.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/index.ts index 6c2f70c5e..e5144d97d 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/index.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/index.ts @@ -1,11 +1,11 @@ -import { AUTH_CORE_AUTH_MODULE_PATHS } from './template-paths.js'; -import { AUTH_CORE_AUTH_MODULE_RENDERERS } from './template-renderers.js'; -import { AUTH_CORE_AUTH_MODULE_IMPORTS } from './ts-import-providers.js'; -import { AUTH_CORE_AUTH_MODULE_TEMPLATES } from './typed-templates.js'; +import { LOCAL_AUTH_CORE_AUTH_MODULE_PATHS } from './template-paths.js'; +import { LOCAL_AUTH_CORE_AUTH_MODULE_RENDERERS } from './template-renderers.js'; +import { LOCAL_AUTH_CORE_AUTH_MODULE_IMPORTS } from './ts-import-providers.js'; +import { LOCAL_AUTH_CORE_AUTH_MODULE_TEMPLATES } from './typed-templates.js'; -export const AUTH_CORE_AUTH_MODULE_GENERATED = { - imports: AUTH_CORE_AUTH_MODULE_IMPORTS, - paths: AUTH_CORE_AUTH_MODULE_PATHS, - renderers: AUTH_CORE_AUTH_MODULE_RENDERERS, - templates: AUTH_CORE_AUTH_MODULE_TEMPLATES, +export const LOCAL_AUTH_CORE_AUTH_MODULE_GENERATED = { + imports: LOCAL_AUTH_CORE_AUTH_MODULE_IMPORTS, + paths: LOCAL_AUTH_CORE_AUTH_MODULE_PATHS, + renderers: LOCAL_AUTH_CORE_AUTH_MODULE_RENDERERS, + templates: LOCAL_AUTH_CORE_AUTH_MODULE_TEMPLATES, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/template-paths.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/template-paths.ts index a50c62ff3..b5f068261 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/template-paths.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/template-paths.ts @@ -1,7 +1,8 @@ import { appModuleProvider } from '@baseplate-dev/fastify-generators'; import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; -export interface AuthCoreAuthModulePaths { +export interface LocalAuthCoreAuthModulePaths { + authRoleEnum: string; cookieSigner: string; schemaUserSessionMutations: string; schemaUserSessionPayloadObjectType: string; @@ -12,19 +13,23 @@ export interface AuthCoreAuthModulePaths { verifyRequestOrigin: string; } -const authCoreAuthModulePaths = createProviderType( - 'auth-core-auth-module-paths', -); +const localAuthCoreAuthModulePaths = + createProviderType( + 'local-auth-core-auth-module-paths', + ); -const authCoreAuthModulePathsTask = createGeneratorTask({ +const localAuthCoreAuthModulePathsTask = createGeneratorTask({ dependencies: { appModule: appModuleProvider }, - exports: { authCoreAuthModulePaths: authCoreAuthModulePaths.export() }, + exports: { + localAuthCoreAuthModulePaths: localAuthCoreAuthModulePaths.export(), + }, run({ appModule }) { const moduleRoot = appModule.getModuleFolder(); return { providers: { - authCoreAuthModulePaths: { + localAuthCoreAuthModulePaths: { + authRoleEnum: `${moduleRoot}/schema/auth-role.enum.ts`, cookieSigner: `${moduleRoot}/utils/cookie-signer.ts`, schemaUserSessionMutations: `${moduleRoot}/schema/user-session.mutations.ts`, schemaUserSessionPayloadObjectType: `${moduleRoot}/schema/user-session-payload.object-type.ts`, @@ -39,7 +44,7 @@ const authCoreAuthModulePathsTask = createGeneratorTask({ }, }); -export const AUTH_CORE_AUTH_MODULE_PATHS = { - provider: authCoreAuthModulePaths, - task: authCoreAuthModulePathsTask, +export const LOCAL_AUTH_CORE_AUTH_MODULE_PATHS = { + provider: localAuthCoreAuthModulePaths, + task: localAuthCoreAuthModulePathsTask, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/template-renderers.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/template-renderers.ts index 6f32fa518..94e462e6f 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/template-renderers.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/template-renderers.ts @@ -11,20 +11,21 @@ import { configServiceImportsProvider, errorHandlerServiceImportsProvider, pothosImportsProvider, + prismaImportsProvider, requestServiceContextImportsProvider, userSessionTypesImportsProvider, } from '@baseplate-dev/fastify-generators'; import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; -import { AUTH_CORE_AUTH_MODULE_PATHS } from './template-paths.js'; -import { AUTH_CORE_AUTH_MODULE_TEMPLATES } from './typed-templates.js'; +import { LOCAL_AUTH_CORE_AUTH_MODULE_PATHS } from './template-paths.js'; +import { LOCAL_AUTH_CORE_AUTH_MODULE_TEMPLATES } from './typed-templates.js'; -export interface AuthCoreAuthModuleRenderers { +export interface LocalAuthCoreAuthModuleRenderers { constantsGroup: { render: ( options: Omit< RenderTsTemplateGroupActionInput< - typeof AUTH_CORE_AUTH_MODULE_TEMPLATES.constantsGroup + typeof LOCAL_AUTH_CORE_AUTH_MODULE_TEMPLATES.constantsGroup >, 'importMapProviders' | 'group' | 'paths' | 'generatorPaths' >, @@ -34,7 +35,7 @@ export interface AuthCoreAuthModuleRenderers { render: ( options: Omit< RenderTsTemplateGroupActionInput< - typeof AUTH_CORE_AUTH_MODULE_TEMPLATES.moduleGroup + typeof LOCAL_AUTH_CORE_AUTH_MODULE_TEMPLATES.moduleGroup >, 'importMapProviders' | 'group' | 'paths' | 'generatorPaths' >, @@ -44,7 +45,7 @@ export interface AuthCoreAuthModuleRenderers { render: ( options: Omit< RenderTsTemplateFileActionInput< - typeof AUTH_CORE_AUTH_MODULE_TEMPLATES.userSessionService + typeof LOCAL_AUTH_CORE_AUTH_MODULE_TEMPLATES.userSessionService >, 'destination' | 'importMapProviders' | 'template' | 'generatorPaths' >, @@ -54,7 +55,7 @@ export interface AuthCoreAuthModuleRenderers { render: ( options: Omit< RenderTsTemplateGroupActionInput< - typeof AUTH_CORE_AUTH_MODULE_TEMPLATES.utilsGroup + typeof LOCAL_AUTH_CORE_AUTH_MODULE_TEMPLATES.utilsGroup >, 'importMapProviders' | 'group' | 'paths' | 'generatorPaths' >, @@ -62,25 +63,26 @@ export interface AuthCoreAuthModuleRenderers { }; } -const authCoreAuthModuleRenderers = - createProviderType( - 'auth-core-auth-module-renderers', +const localAuthCoreAuthModuleRenderers = + createProviderType( + 'local-auth-core-auth-module-renderers', ); -const authCoreAuthModuleRenderersTask = createGeneratorTask({ +const localAuthCoreAuthModuleRenderersTask = createGeneratorTask({ dependencies: { authContextImports: authContextImportsProvider, authRolesImports: authRolesImportsProvider, configServiceImports: configServiceImportsProvider, errorHandlerServiceImports: errorHandlerServiceImportsProvider, - paths: AUTH_CORE_AUTH_MODULE_PATHS.provider, + paths: LOCAL_AUTH_CORE_AUTH_MODULE_PATHS.provider, pothosImports: pothosImportsProvider, + prismaImports: prismaImportsProvider, requestServiceContextImports: requestServiceContextImportsProvider, typescriptFile: typescriptFileProvider, userSessionTypesImports: userSessionTypesImportsProvider, }, exports: { - authCoreAuthModuleRenderers: authCoreAuthModuleRenderers.export(), + localAuthCoreAuthModuleRenderers: localAuthCoreAuthModuleRenderers.export(), }, run({ authContextImports, @@ -89,17 +91,18 @@ const authCoreAuthModuleRenderersTask = createGeneratorTask({ errorHandlerServiceImports, paths, pothosImports, + prismaImports, requestServiceContextImports, typescriptFile, userSessionTypesImports, }) { return { providers: { - authCoreAuthModuleRenderers: { + localAuthCoreAuthModuleRenderers: { constantsGroup: { render: (options) => typescriptFile.renderTemplateGroup({ - group: AUTH_CORE_AUTH_MODULE_TEMPLATES.constantsGroup, + group: LOCAL_AUTH_CORE_AUTH_MODULE_TEMPLATES.constantsGroup, paths, ...options, }), @@ -107,9 +110,10 @@ const authCoreAuthModuleRenderersTask = createGeneratorTask({ moduleGroup: { render: (options) => typescriptFile.renderTemplateGroup({ - group: AUTH_CORE_AUTH_MODULE_TEMPLATES.moduleGroup, + group: LOCAL_AUTH_CORE_AUTH_MODULE_TEMPLATES.moduleGroup, paths, importMapProviders: { + authRolesImports, pothosImports, }, generatorPaths: paths, @@ -119,13 +123,15 @@ const authCoreAuthModuleRenderersTask = createGeneratorTask({ userSessionService: { render: (options) => typescriptFile.renderTemplateFile({ - template: AUTH_CORE_AUTH_MODULE_TEMPLATES.userSessionService, + template: + LOCAL_AUTH_CORE_AUTH_MODULE_TEMPLATES.userSessionService, destination: paths.userSessionService, importMapProviders: { authContextImports, authRolesImports, configServiceImports, errorHandlerServiceImports, + prismaImports, requestServiceContextImports, userSessionTypesImports, }, @@ -136,7 +142,7 @@ const authCoreAuthModuleRenderersTask = createGeneratorTask({ utilsGroup: { render: (options) => typescriptFile.renderTemplateGroup({ - group: AUTH_CORE_AUTH_MODULE_TEMPLATES.utilsGroup, + group: LOCAL_AUTH_CORE_AUTH_MODULE_TEMPLATES.utilsGroup, paths, importMapProviders: { configServiceImports, @@ -150,7 +156,7 @@ const authCoreAuthModuleRenderersTask = createGeneratorTask({ }, }); -export const AUTH_CORE_AUTH_MODULE_RENDERERS = { - provider: authCoreAuthModuleRenderers, - task: authCoreAuthModuleRenderersTask, +export const LOCAL_AUTH_CORE_AUTH_MODULE_RENDERERS = { + provider: localAuthCoreAuthModuleRenderers, + task: localAuthCoreAuthModuleRenderersTask, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/ts-import-providers.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/ts-import-providers.ts index f5bbee5cc..4e838bdd4 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/ts-import-providers.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/ts-import-providers.ts @@ -14,7 +14,7 @@ import { createReadOnlyProviderType, } from '@baseplate-dev/sync'; -import { AUTH_CORE_AUTH_MODULE_PATHS } from './template-paths.js'; +import { LOCAL_AUTH_CORE_AUTH_MODULE_PATHS } from './template-paths.js'; const authModuleImportsSchema = createTsImportMapSchema({ userSessionPayload: {}, @@ -27,9 +27,9 @@ export type AuthModuleImportsProvider = TsImportMapProviderFromSchema< export const authModuleImportsProvider = createReadOnlyProviderType('auth-module-imports'); -const authCoreAuthModuleImportsTask = createGeneratorTask({ +const localAuthCoreAuthModuleImportsTask = createGeneratorTask({ dependencies: { - paths: AUTH_CORE_AUTH_MODULE_PATHS.provider, + paths: LOCAL_AUTH_CORE_AUTH_MODULE_PATHS.provider, }, exports: { authModuleImports: authModuleImportsProvider.export(packageScope), @@ -51,6 +51,6 @@ const authCoreAuthModuleImportsTask = createGeneratorTask({ }, }); -export const AUTH_CORE_AUTH_MODULE_IMPORTS = { - task: authCoreAuthModuleImportsTask, +export const LOCAL_AUTH_CORE_AUTH_MODULE_IMPORTS = { + task: localAuthCoreAuthModuleImportsTask, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/typed-templates.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/typed-templates.ts index a4a17a598..de6e04719 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/typed-templates.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/generated/typed-templates.ts @@ -5,6 +5,7 @@ import { configServiceImportsProvider, errorHandlerServiceImportsProvider, pothosImportsProvider, + prismaImportsProvider, requestServiceContextImportsProvider, userSessionTypesImportsProvider, } from '@baseplate-dev/fastify-generators'; @@ -26,6 +27,24 @@ const userSessionConstants = createTsTemplateFile({ export const constantsGroup = { userSessionConstants }; +const authRoleEnum = createTsTemplateFile({ + fileOptions: { kind: 'singleton' }, + group: 'module', + importMapProviders: { + authRolesImports: authRolesImportsProvider, + pothosImports: pothosImportsProvider, + }, + name: 'auth-role-enum', + projectExports: {}, + source: { + path: path.join( + import.meta.dirname, + '../templates/module/schema/auth-role.enum.ts', + ), + }, + variables: {}, +}); + const schemaUserSessionMutations = createTsTemplateFile({ fileOptions: { kind: 'singleton' }, group: 'module', @@ -47,6 +66,7 @@ const schemaUserSessionPayloadObjectType = createTsTemplateFile({ importMapProviders: { pothosImports: pothosImportsProvider }, name: 'schema-user-session-payload-object-type', projectExports: { userSessionPayload: {} }, + referencedGeneratorTemplates: { authRoleEnum: {} }, source: { path: path.join( import.meta.dirname, @@ -72,6 +92,7 @@ const schemaUserSessionQueries = createTsTemplateFile({ }); export const moduleGroup = { + authRoleEnum, schemaUserSessionMutations, schemaUserSessionPayloadObjectType, schemaUserSessionQueries, @@ -84,6 +105,7 @@ const userSessionService = createTsTemplateFile({ authRolesImports: authRolesImportsProvider, configServiceImports: configServiceImportsProvider, errorHandlerServiceImports: errorHandlerServiceImportsProvider, + prismaImports: prismaImportsProvider, requestServiceContextImports: requestServiceContextImportsProvider, userSessionTypesImports: userSessionTypesImportsProvider, }, @@ -148,7 +170,7 @@ const verifyRequestOrigin = createTsTemplateFile({ export const utilsGroup = { cookieSigner, sessionCookie, verifyRequestOrigin }; -export const AUTH_CORE_AUTH_MODULE_TEMPLATES = { +export const LOCAL_AUTH_CORE_AUTH_MODULE_TEMPLATES = { constantsGroup, moduleGroup, userSessionService, diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/schema/auth-role.enum.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/schema/auth-role.enum.ts new file mode 100644 index 000000000..f2be5ef64 --- /dev/null +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/schema/auth-role.enum.ts @@ -0,0 +1,21 @@ +// @ts-nocheck + +import type { AuthRole } from '%authRolesImports'; + +import { AUTH_ROLE_CONFIG } from '%authRolesImports'; +import { builder } from '%pothosImports'; + +/** + * GraphQL enum for authentication roles. + * + * This enum represents all available roles that can be assigned to users + * or used for authorization checks in the application. + */ +export const authRoleEnum = builder.enumType('AuthRole', { + values: Object.fromEntries( + Object.entries(AUTH_ROLE_CONFIG).map(([role, config]) => [ + role, + { value: role, description: config.comment }, + ]), + ) as Record, +}); diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/schema/user-session-payload.object-type.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/schema/user-session-payload.object-type.ts index b86c885e3..dc89eb696 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/schema/user-session-payload.object-type.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/schema/user-session-payload.object-type.ts @@ -1,5 +1,6 @@ // @ts-nocheck +import { authRoleEnum } from '$authRoleEnum'; import { builder } from '%pothosImports'; export const userSessionPayload = builder.simpleObject( @@ -8,6 +9,7 @@ export const userSessionPayload = builder.simpleObject( fields: (t) => ({ expiresAt: t.field({ type: 'DateTime', nullable: true }), userId: t.field({ type: 'Uuid' }), + roles: t.field({ type: [authRoleEnum] }), }), }, (t) => ({ diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/schema/user-session.queries.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/schema/user-session.queries.ts index b75ec12ab..8a09a7e88 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/schema/user-session.queries.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/schema/user-session.queries.ts @@ -17,6 +17,7 @@ builder.queryField('currentUserSession', (t) => return { expiresAt: auth.session.expiresAt?.toISOString(), userId: auth.session.userId, + roles: auth.roles, }; }, }), diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/services/user-session.service.ts b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/services/user-session.service.ts index 4b8922449..6a9d78de7 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/services/user-session.service.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-module/templates/module/services/user-session.service.ts @@ -22,6 +22,7 @@ import { InvalidSessionError } from '%authContextImports'; import { DEFAULT_USER_ROLES } from '%authRolesImports'; import { config } from '%configServiceImports'; import { ForbiddenError } from '%errorHandlerServiceImports'; +import { prisma } from '%prismaImports'; import { randomBytes } from 'node:crypto'; interface SessionCookieValue { @@ -116,12 +117,23 @@ export class CookieUserSessionService implements UserSessionService { }, }); + // Fetch user roles to include in the session payload + const user = await prisma.user.findUniqueOrThrow({ + where: { id: userId }, + select: { roles: true }, + }); + + const roles: readonly AuthRole[] = [ + ...DEFAULT_USER_ROLES, + ...user.roles.map((role) => role.role as AuthRole), + ]; + const cookieName = getUserSessionCookieName(context.reqInfo.headers); const cookieValue = signObject({ token }, config.AUTH_SECRET); context.cookieStore.set(cookieName, cookieValue, COOKIE_OPTIONS); - return { userId, expiresAt }; + return { userId, expiresAt, roles }; } /** diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/extractor.json b/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/extractor.json index 3b421924e..c43c483b0 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/extractor.json +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/extractor.json @@ -4,7 +4,7 @@ "login": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-routes", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-routes", "group": "main", "importMapProviders": { "apolloErrorImportsProvider": { @@ -25,7 +25,7 @@ }, "reactSessionImportsProvider": { "importName": "reactSessionImportsProvider", - "packagePathSpecifier": "@baseplate-dev/plugin-auth:src/auth/core/generators/react-session/generated/ts-import-providers.ts" + "packagePathSpecifier": "@baseplate-dev/plugin-auth:src/local-auth/core/generators/react-session/generated/ts-import-providers.ts" } }, "pathRootRelativePath": "{routes-root}/auth_/login.tsx", @@ -42,7 +42,7 @@ "register": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-routes", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-routes", "group": "main", "importMapProviders": { "apolloErrorImportsProvider": { @@ -63,7 +63,7 @@ }, "reactSessionImportsProvider": { "importName": "reactSessionImportsProvider", - "packagePathSpecifier": "@baseplate-dev/plugin-auth:src/auth/core/generators/react-session/generated/ts-import-providers.ts" + "packagePathSpecifier": "@baseplate-dev/plugin-auth:src/local-auth/core/generators/react-session/generated/ts-import-providers.ts" } }, "pathRootRelativePath": "{routes-root}/auth_/register.tsx", @@ -73,7 +73,7 @@ "route": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/auth-routes", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/auth-routes", "group": "main", "importMapProviders": {}, "pathRootRelativePath": "{routes-root}/auth_/route.tsx", diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/templates/routes/auth_/login.tsx b/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/templates/routes/auth_/login.tsx index e870cbb05..c7619e91b 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/templates/routes/auth_/login.tsx +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/templates/routes/auth_/login.tsx @@ -12,7 +12,7 @@ import { InputFieldController, } from '%reactComponentsImports'; import { logAndFormatError, logError } from '%reactErrorImports'; -import { useUserSessionClient } from '%reactSessionImports'; +import { userSessionClient } from '%reactSessionImports'; import { useMutation } from '@apollo/client'; import { zodResolver } from '@hookform/resolvers/zod'; import { @@ -65,7 +65,6 @@ function LoginPage(): React.JSX.Element { const [loginWithEmailPassword, { loading }] = useMutation( LoginWithEmailPasswordDocument, ); - const { client } = useUserSessionClient(); const navigate = useNavigate(); const { return_to } = Route.useSearch(); @@ -83,7 +82,7 @@ function LoginPage(): React.JSX.Element { throw new Error('No data returned from login mutation'); } const { userId } = data.loginWithEmailPassword.session; - client.signIn(userId); + userSessionClient.signIn(userId); navigate({ to: return_to ?? '/', replace: true }).catch(logError); }) diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/templates/routes/auth_/queries.gql b/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/templates/routes/auth_/queries.gql index 297957717..b6b9a0b16 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/templates/routes/auth_/queries.gql +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/templates/routes/auth_/queries.gql @@ -2,6 +2,7 @@ mutation RegisterWithEmailPassword($input: RegisterWithEmailPasswordInput!) { registerWithEmailPassword(input: $input) { session { userId + roles } } } @@ -10,6 +11,7 @@ mutation LoginWithEmailPassword($input: LoginWithEmailPasswordInput!) { loginWithEmailPassword(input: $input) { session { userId + roles } } } diff --git a/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/templates/routes/auth_/register.tsx b/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/templates/routes/auth_/register.tsx index 618aa54ba..2447129ee 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/templates/routes/auth_/register.tsx +++ b/plugins/plugin-auth/src/local-auth/core/generators/auth-routes/templates/routes/auth_/register.tsx @@ -12,7 +12,7 @@ import { InputFieldController, } from '%reactComponentsImports'; import { logAndFormatError, logError } from '%reactErrorImports'; -import { useUserSessionClient } from '%reactSessionImports'; +import { userSessionClient } from '%reactSessionImports'; import { useMutation } from '@apollo/client'; import { zodResolver } from '@hookform/resolvers/zod'; import { @@ -64,7 +64,6 @@ function RegisterPage(): React.JSX.Element { const [registerWithEmailPassword, { loading }] = useMutation( RegisterWithEmailPasswordDocument, ); - const { client } = useUserSessionClient(); const navigate = useNavigate(); const { return_to } = Route.useSearch(); @@ -82,7 +81,7 @@ function RegisterPage(): React.JSX.Element { throw new Error('No data returned from login mutation'); } const { userId } = data.registerWithEmailPassword.session; - client.signIn(userId); + userSessionClient.signIn(userId); navigate({ to: return_to ?? '/', replace: true }).catch(logError); }) diff --git a/plugins/plugin-auth/src/local-auth/core/generators/react-auth/extractor.json b/plugins/plugin-auth/src/local-auth/core/generators/react-auth/extractor.json index 98a676921..9df821795 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/react-auth/extractor.json +++ b/plugins/plugin-auth/src/local-auth/core/generators/react-auth/extractor.json @@ -1,3 +1,3 @@ { - "name": "auth/core/react-auth" + "name": "local-auth/core/react-auth" } diff --git a/plugins/plugin-auth/src/local-auth/core/generators/react-auth/react-auth.generator.ts b/plugins/plugin-auth/src/local-auth/core/generators/react-auth/react-auth.generator.ts index ad5443fc4..b2266147e 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/react-auth/react-auth.generator.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/react-auth/react-auth.generator.ts @@ -9,7 +9,7 @@ const descriptorSchema = z.object({}); * Generator for basic React auth integrations */ export const reactAuthGenerator = createGenerator({ - name: 'react/react-auth', + name: 'local-auth/core/react-auth', generatorFileUrl: import.meta.url, descriptorSchema, buildTasks: () => ({ diff --git a/plugins/plugin-auth/src/local-auth/core/generators/react-session/extractor.json b/plugins/plugin-auth/src/local-auth/core/generators/react-session/extractor.json index 4eaba01c4..a88571187 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/react-session/extractor.json +++ b/plugins/plugin-auth/src/local-auth/core/generators/react-session/extractor.json @@ -1,49 +1,10 @@ { - "name": "auth/core/react-session", + "name": "local-auth/core/react-session", "templates": { - "use-user-session-client": { - "type": "ts", - "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/react-session", - "group": "main", - "importMapProviders": {}, - "pathRootRelativePath": "{src-root}/hooks/use-user-session-client.ts", - "projectExports": { - "UserSessionClientContext": {}, - "UserSessionClientContextValue": { "isTypeOnly": true }, - "useUserSessionClient": {} - }, - "referencedGeneratorTemplates": ["user-session-client"], - "sourceFile": "src/hooks/use-user-session-client.ts", - "variables": {} - }, - "user-session-check": { - "type": "ts", - "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/react-session", - "group": "main", - "importMapProviders": { - "generatedGraphqlImportsProvider": { - "importName": "generatedGraphqlImportsProvider", - "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/apollo/react-apollo/providers/generated-graphql.ts" - } - }, - "pathRootRelativePath": "{src-root}/app/user-session-check.tsx", - "referencedGeneratorTemplates": ["use-user-session-client"], - "sourceFile": "src/app/user-session-check.tsx", - "variables": {} - }, - "user-session-check-gql": { - "type": "text", - "fileOptions": { "kind": "singleton" }, - "pathRootRelativePath": "{src-root}/app/user-session-check.gql", - "sourceFile": "src/app/user-session-check.gql", - "variables": {} - }, "user-session-client": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/react-session", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/react-session", "group": "main", "importMapProviders": { "reactUtilsImportsProvider": { @@ -52,29 +13,48 @@ } }, "pathRootRelativePath": "{src-root}/services/user-session-client.ts", - "projectExports": { - "createUserSessionClient": {}, - "SessionChangeCallback": { "isTypeOnly": true }, - "UserSessionClient": {}, - "UserSessionClientConfig": { "isTypeOnly": true }, - "UserSessionData": { "isTypeOnly": true } - }, + "projectExports": { "userSessionClient": {}, "UserSessionClient": {} }, "sourceFile": "src/services/user-session-client.ts", "variables": {} }, "user-session-provider": { "type": "ts", "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/plugin-auth#auth/core/react-session", + "generator": "@baseplate-dev/plugin-auth#local-auth/core/react-session", "group": "main", - "importMapProviders": {}, + "importMapProviders": { + "authHooksImportsProvider": { + "importName": "authHooksImportsProvider", + "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/auth/_providers/auth-hooks.ts" + }, + "generatedGraphqlImportsProvider": { + "importName": "generatedGraphqlImportsProvider", + "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/apollo/react-apollo/providers/generated-graphql.ts" + }, + "localAuthHooksImportsProvider": { + "importName": "localAuthHooksImportsProvider", + "packagePathSpecifier": "@baseplate-dev/plugin-auth:src/local-auth/core/generators/auth-hooks/generated/ts-import-providers.ts" + }, + "reactComponentsImportsProvider": { + "importName": "reactComponentsImportsProvider", + "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/core/react-components/generated/ts-import-providers.ts" + }, + "reactErrorImportsProvider": { + "importName": "reactErrorImportsProvider", + "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/core/react-error/generated/ts-import-providers.ts" + } + }, "pathRootRelativePath": "{src-root}/app/user-session-provider.tsx", - "referencedGeneratorTemplates": [ - "use-user-session-client", - "user-session-client" - ], + "referencedGeneratorTemplates": ["user-session-client"], "sourceFile": "src/app/user-session-provider.tsx", "variables": {} + }, + "user-session-provider-gql": { + "type": "text", + "fileOptions": { "kind": "singleton" }, + "pathRootRelativePath": "{src-root}/app/user-session-provider.gql", + "sourceFile": "src/app/user-session-provider.gql", + "variables": {} } } } diff --git a/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/index.ts b/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/index.ts index a559c1e7e..af4544d99 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/index.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/index.ts @@ -1,11 +1,11 @@ -import { AUTH_CORE_REACT_SESSION_PATHS } from './template-paths.js'; -import { AUTH_CORE_REACT_SESSION_RENDERERS } from './template-renderers.js'; -import { AUTH_CORE_REACT_SESSION_IMPORTS } from './ts-import-providers.js'; -import { AUTH_CORE_REACT_SESSION_TEMPLATES } from './typed-templates.js'; +import { LOCAL_AUTH_CORE_REACT_SESSION_PATHS } from './template-paths.js'; +import { LOCAL_AUTH_CORE_REACT_SESSION_RENDERERS } from './template-renderers.js'; +import { LOCAL_AUTH_CORE_REACT_SESSION_IMPORTS } from './ts-import-providers.js'; +import { LOCAL_AUTH_CORE_REACT_SESSION_TEMPLATES } from './typed-templates.js'; -export const AUTH_CORE_REACT_SESSION_GENERATED = { - imports: AUTH_CORE_REACT_SESSION_IMPORTS, - paths: AUTH_CORE_REACT_SESSION_PATHS, - renderers: AUTH_CORE_REACT_SESSION_RENDERERS, - templates: AUTH_CORE_REACT_SESSION_TEMPLATES, +export const LOCAL_AUTH_CORE_REACT_SESSION_GENERATED = { + imports: LOCAL_AUTH_CORE_REACT_SESSION_IMPORTS, + paths: LOCAL_AUTH_CORE_REACT_SESSION_PATHS, + renderers: LOCAL_AUTH_CORE_REACT_SESSION_RENDERERS, + templates: LOCAL_AUTH_CORE_REACT_SESSION_TEMPLATES, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/template-paths.ts b/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/template-paths.ts index 62040fd4a..959324f60 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/template-paths.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/template-paths.ts @@ -1,39 +1,38 @@ import { packageInfoProvider } from '@baseplate-dev/core-generators'; import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; -export interface AuthCoreReactSessionPaths { - userSessionCheck: string; - userSessionCheckGql: string; +export interface LocalAuthCoreReactSessionPaths { userSessionClient: string; userSessionProvider: string; - useUserSessionClient: string; + userSessionProviderGql: string; } -const authCoreReactSessionPaths = createProviderType( - 'auth-core-react-session-paths', -); +const localAuthCoreReactSessionPaths = + createProviderType( + 'local-auth-core-react-session-paths', + ); -const authCoreReactSessionPathsTask = createGeneratorTask({ +const localAuthCoreReactSessionPathsTask = createGeneratorTask({ dependencies: { packageInfo: packageInfoProvider }, - exports: { authCoreReactSessionPaths: authCoreReactSessionPaths.export() }, + exports: { + localAuthCoreReactSessionPaths: localAuthCoreReactSessionPaths.export(), + }, run({ packageInfo }) { const srcRoot = packageInfo.getPackageSrcPath(); return { providers: { - authCoreReactSessionPaths: { - userSessionCheck: `${srcRoot}/app/user-session-check.tsx`, - userSessionCheckGql: `${srcRoot}/app/user-session-check.gql`, + localAuthCoreReactSessionPaths: { userSessionClient: `${srcRoot}/services/user-session-client.ts`, userSessionProvider: `${srcRoot}/app/user-session-provider.tsx`, - useUserSessionClient: `${srcRoot}/hooks/use-user-session-client.ts`, + userSessionProviderGql: `${srcRoot}/app/user-session-provider.gql`, }, }, }; }, }); -export const AUTH_CORE_REACT_SESSION_PATHS = { - provider: authCoreReactSessionPaths, - task: authCoreReactSessionPathsTask, +export const LOCAL_AUTH_CORE_REACT_SESSION_PATHS = { + provider: localAuthCoreReactSessionPaths, + task: localAuthCoreReactSessionPathsTask, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/template-renderers.ts b/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/template-renderers.ts index d284d0bc1..9ac42de66 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/template-renderers.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/template-renderers.ts @@ -9,30 +9,35 @@ import { typescriptFileProvider, } from '@baseplate-dev/core-generators'; import { + authHooksImportsProvider, generatedGraphqlImportsProvider, + reactComponentsImportsProvider, + reactErrorImportsProvider, reactUtilsImportsProvider, } from '@baseplate-dev/react-generators'; import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; -import { AUTH_CORE_REACT_SESSION_PATHS } from './template-paths.js'; -import { AUTH_CORE_REACT_SESSION_TEMPLATES } from './typed-templates.js'; +import { localAuthHooksImportsProvider } from '#src/local-auth/core/generators/auth-hooks/generated/ts-import-providers.js'; -export interface AuthCoreReactSessionRenderers { +import { LOCAL_AUTH_CORE_REACT_SESSION_PATHS } from './template-paths.js'; +import { LOCAL_AUTH_CORE_REACT_SESSION_TEMPLATES } from './typed-templates.js'; + +export interface LocalAuthCoreReactSessionRenderers { mainGroup: { render: ( options: Omit< RenderTsTemplateGroupActionInput< - typeof AUTH_CORE_REACT_SESSION_TEMPLATES.mainGroup + typeof LOCAL_AUTH_CORE_REACT_SESSION_TEMPLATES.mainGroup >, 'importMapProviders' | 'group' | 'paths' | 'generatorPaths' >, ) => BuilderAction; }; - userSessionCheckGql: { + userSessionProviderGql: { render: ( options: Omit< RenderTextTemplateFileActionInput< - typeof AUTH_CORE_REACT_SESSION_TEMPLATES.userSessionCheckGql + typeof LOCAL_AUTH_CORE_REACT_SESSION_TEMPLATES.userSessionProviderGql >, 'destination' | 'template' >, @@ -40,43 +45,62 @@ export interface AuthCoreReactSessionRenderers { }; } -const authCoreReactSessionRenderers = - createProviderType( - 'auth-core-react-session-renderers', +const localAuthCoreReactSessionRenderers = + createProviderType( + 'local-auth-core-react-session-renderers', ); -const authCoreReactSessionRenderersTask = createGeneratorTask({ +const localAuthCoreReactSessionRenderersTask = createGeneratorTask({ dependencies: { + authHooksImports: authHooksImportsProvider, generatedGraphqlImports: generatedGraphqlImportsProvider, - paths: AUTH_CORE_REACT_SESSION_PATHS.provider, + localAuthHooksImports: localAuthHooksImportsProvider, + paths: LOCAL_AUTH_CORE_REACT_SESSION_PATHS.provider, + reactComponentsImports: reactComponentsImportsProvider, + reactErrorImports: reactErrorImportsProvider, reactUtilsImports: reactUtilsImportsProvider, typescriptFile: typescriptFileProvider, }, exports: { - authCoreReactSessionRenderers: authCoreReactSessionRenderers.export(), + localAuthCoreReactSessionRenderers: + localAuthCoreReactSessionRenderers.export(), }, - run({ generatedGraphqlImports, paths, reactUtilsImports, typescriptFile }) { + run({ + authHooksImports, + generatedGraphqlImports, + localAuthHooksImports, + paths, + reactComponentsImports, + reactErrorImports, + reactUtilsImports, + typescriptFile, + }) { return { providers: { - authCoreReactSessionRenderers: { + localAuthCoreReactSessionRenderers: { mainGroup: { render: (options) => typescriptFile.renderTemplateGroup({ - group: AUTH_CORE_REACT_SESSION_TEMPLATES.mainGroup, + group: LOCAL_AUTH_CORE_REACT_SESSION_TEMPLATES.mainGroup, paths, importMapProviders: { + authHooksImports, generatedGraphqlImports, + localAuthHooksImports, + reactComponentsImports, + reactErrorImports, reactUtilsImports, }, generatorPaths: paths, ...options, }), }, - userSessionCheckGql: { + userSessionProviderGql: { render: (options) => renderTextTemplateFileAction({ - template: AUTH_CORE_REACT_SESSION_TEMPLATES.userSessionCheckGql, - destination: paths.userSessionCheckGql, + template: + LOCAL_AUTH_CORE_REACT_SESSION_TEMPLATES.userSessionProviderGql, + destination: paths.userSessionProviderGql, ...options, }), }, @@ -86,7 +110,7 @@ const authCoreReactSessionRenderersTask = createGeneratorTask({ }, }); -export const AUTH_CORE_REACT_SESSION_RENDERERS = { - provider: authCoreReactSessionRenderers, - task: authCoreReactSessionRenderersTask, +export const LOCAL_AUTH_CORE_REACT_SESSION_RENDERERS = { + provider: localAuthCoreReactSessionRenderers, + task: localAuthCoreReactSessionRenderersTask, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/ts-import-providers.ts b/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/ts-import-providers.ts index 3b3aff5ac..473a5baf7 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/ts-import-providers.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/ts-import-providers.ts @@ -10,17 +10,11 @@ import { createReadOnlyProviderType, } from '@baseplate-dev/sync'; -import { AUTH_CORE_REACT_SESSION_PATHS } from './template-paths.js'; +import { LOCAL_AUTH_CORE_REACT_SESSION_PATHS } from './template-paths.js'; const reactSessionImportsSchema = createTsImportMapSchema({ - createUserSessionClient: {}, - SessionChangeCallback: { isTypeOnly: true }, + userSessionClient: {}, UserSessionClient: {}, - UserSessionClientConfig: { isTypeOnly: true }, - UserSessionClientContext: {}, - UserSessionClientContextValue: { isTypeOnly: true }, - UserSessionData: { isTypeOnly: true }, - useUserSessionClient: {}, }); export type ReactSessionImportsProvider = TsImportMapProviderFromSchema< @@ -32,9 +26,9 @@ export const reactSessionImportsProvider = 'react-session-imports', ); -const authCoreReactSessionImportsTask = createGeneratorTask({ +const localAuthCoreReactSessionImportsTask = createGeneratorTask({ dependencies: { - paths: AUTH_CORE_REACT_SESSION_PATHS.provider, + paths: LOCAL_AUTH_CORE_REACT_SESSION_PATHS.provider, }, exports: { reactSessionImports: reactSessionImportsProvider.export(packageScope), @@ -43,20 +37,14 @@ const authCoreReactSessionImportsTask = createGeneratorTask({ return { providers: { reactSessionImports: createTsImportMap(reactSessionImportsSchema, { - createUserSessionClient: paths.userSessionClient, - SessionChangeCallback: paths.userSessionClient, + userSessionClient: paths.userSessionClient, UserSessionClient: paths.userSessionClient, - UserSessionClientConfig: paths.userSessionClient, - UserSessionClientContext: paths.useUserSessionClient, - UserSessionClientContextValue: paths.useUserSessionClient, - UserSessionData: paths.userSessionClient, - useUserSessionClient: paths.useUserSessionClient, }), }, }; }, }); -export const AUTH_CORE_REACT_SESSION_IMPORTS = { - task: authCoreReactSessionImportsTask, +export const LOCAL_AUTH_CORE_REACT_SESSION_IMPORTS = { + task: localAuthCoreReactSessionImportsTask, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/typed-templates.ts b/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/typed-templates.ts index eacb1f3f2..c3feaadce 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/typed-templates.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/react-session/generated/typed-templates.ts @@ -3,40 +3,22 @@ import { createTsTemplateFile, } from '@baseplate-dev/core-generators'; import { + authHooksImportsProvider, generatedGraphqlImportsProvider, + reactComponentsImportsProvider, + reactErrorImportsProvider, reactUtilsImportsProvider, } from '@baseplate-dev/react-generators'; import path from 'node:path'; -const userSessionCheck = createTsTemplateFile({ - fileOptions: { kind: 'singleton' }, - group: 'main', - importMapProviders: { - generatedGraphqlImports: generatedGraphqlImportsProvider, - }, - name: 'user-session-check', - referencedGeneratorTemplates: { useUserSessionClient: {} }, - source: { - path: path.join( - import.meta.dirname, - '../templates/src/app/user-session-check.tsx', - ), - }, - variables: {}, -}); +import { localAuthHooksImportsProvider } from '#src/local-auth/core/generators/auth-hooks/generated/ts-import-providers.js'; const userSessionClient = createTsTemplateFile({ fileOptions: { kind: 'singleton' }, group: 'main', importMapProviders: { reactUtilsImports: reactUtilsImportsProvider }, name: 'user-session-client', - projectExports: { - createUserSessionClient: {}, - SessionChangeCallback: { isTypeOnly: true }, - UserSessionClient: {}, - UserSessionClientConfig: { isTypeOnly: true }, - UserSessionData: { isTypeOnly: true }, - }, + projectExports: { userSessionClient: {}, UserSessionClient: {} }, source: { path: path.join( import.meta.dirname, @@ -49,61 +31,39 @@ const userSessionClient = createTsTemplateFile({ const userSessionProvider = createTsTemplateFile({ fileOptions: { kind: 'singleton' }, group: 'main', - importMapProviders: {}, - name: 'user-session-provider', - referencedGeneratorTemplates: { - useUserSessionClient: {}, - userSessionClient: {}, - }, - source: { - path: path.join( - import.meta.dirname, - '../templates/src/app/user-session-provider.tsx', - ), - }, - variables: {}, -}); - -const useUserSessionClient = createTsTemplateFile({ - fileOptions: { kind: 'singleton' }, - group: 'main', - importMapProviders: {}, - name: 'use-user-session-client', - projectExports: { - UserSessionClientContext: {}, - UserSessionClientContextValue: { isTypeOnly: true }, - useUserSessionClient: {}, + importMapProviders: { + authHooksImports: authHooksImportsProvider, + generatedGraphqlImports: generatedGraphqlImportsProvider, + localAuthHooksImports: localAuthHooksImportsProvider, + reactComponentsImports: reactComponentsImportsProvider, + reactErrorImports: reactErrorImportsProvider, }, + name: 'user-session-provider', referencedGeneratorTemplates: { userSessionClient: {} }, source: { path: path.join( import.meta.dirname, - '../templates/src/hooks/use-user-session-client.ts', + '../templates/src/app/user-session-provider.tsx', ), }, variables: {}, }); -export const mainGroup = { - userSessionCheck, - userSessionClient, - userSessionProvider, - useUserSessionClient, -}; +export const mainGroup = { userSessionClient, userSessionProvider }; -const userSessionCheckGql = createTextTemplateFile({ +const userSessionProviderGql = createTextTemplateFile({ fileOptions: { kind: 'singleton' }, - name: 'user-session-check-gql', + name: 'user-session-provider-gql', source: { path: path.join( import.meta.dirname, - '../templates/src/app/user-session-check.gql', + '../templates/src/app/user-session-provider.gql', ), }, variables: {}, }); -export const AUTH_CORE_REACT_SESSION_TEMPLATES = { +export const LOCAL_AUTH_CORE_REACT_SESSION_TEMPLATES = { mainGroup, - userSessionCheckGql, + userSessionProviderGql, }; diff --git a/plugins/plugin-auth/src/local-auth/core/generators/react-session/react-session.generator.ts b/plugins/plugin-auth/src/local-auth/core/generators/react-session/react-session.generator.ts index ba46fb50e..bbc58bddb 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/react-session/react-session.generator.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/react-session/react-session.generator.ts @@ -1,13 +1,9 @@ -import { - renderTextTemplateFileAction, - TsCodeUtils, - tsImportBuilder, -} from '@baseplate-dev/core-generators'; +import { TsCodeUtils, tsImportBuilder } from '@baseplate-dev/core-generators'; import { reactAppConfigProvider } from '@baseplate-dev/react-generators'; import { createGenerator, createGeneratorTask } from '@baseplate-dev/sync'; import { z } from 'zod'; -import { AUTH_CORE_REACT_SESSION_GENERATED as GENERATED_TEMPLATES } from './generated/index.js'; +import { LOCAL_AUTH_CORE_REACT_SESSION_GENERATED as GENERATED_TEMPLATES } from './generated/index.js'; const descriptorSchema = z.object({}); @@ -15,7 +11,7 @@ const descriptorSchema = z.object({}); * Generator for React session management */ export const reactSessionGenerator = createGenerator({ - name: 'auth/core/react-session', + name: 'local-auth/core/react-session', generatorFileUrl: import.meta.url, descriptorSchema, buildTasks: () => ({ @@ -35,22 +31,15 @@ export const reactSessionGenerator = createGenerator({ paths.userSessionProvider, ), ])`${contents}`, - type: 'auth', + type: 'router', }); - reactAppConfig.renderSiblings.set( - 'user-seession-check', - TsCodeUtils.templateWithImports([ - tsImportBuilder(['UserSessionCheck']).from(paths.userSessionCheck), - ])``, - ); }, }), main: createGeneratorTask({ dependencies: { renderers: GENERATED_TEMPLATES.renderers.provider, - paths: GENERATED_TEMPLATES.paths.provider, }, - run({ renderers, paths }) { + run({ renderers }) { return { build: async (builder) => { await builder.apply( @@ -58,13 +47,7 @@ export const reactSessionGenerator = createGenerator({ variables: {}, }), ); - await builder.apply( - renderTextTemplateFileAction({ - destination: paths.userSessionCheckGql, - template: GENERATED_TEMPLATES.templates.userSessionCheckGql, - variables: {}, - }), - ); + await builder.apply(renderers.userSessionProviderGql.render({})); }, }; }, diff --git a/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/app/user-session-check.tsx b/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/app/user-session-check.tsx deleted file mode 100644 index b92001023..000000000 --- a/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/app/user-session-check.tsx +++ /dev/null @@ -1,28 +0,0 @@ -// @ts-nocheck - -import { useUserSessionClient } from '$useUserSessionClient'; -import { GetCurrentUserSessionDocument } from '%generatedGraphqlImports'; -import { useQuery } from '@apollo/client'; -import { useEffect } from 'react'; - -/** - * Checks if the user session matches the loaded ID on first page load. - * - * This ensures that we have the correct user ID loaded on first page load. - */ -export function UserSessionCheck(): React.ReactElement | null { - const { data } = useQuery(GetCurrentUserSessionDocument, { - fetchPolicy: 'no-cache', - }); - const { client } = useUserSessionClient(); - - useEffect(() => { - if (!data?.currentUserSession?.userId) return; - - if (data.currentUserSession.userId !== client.getSession()?.userId) { - client.signOut(); - } - }, [data, client]); - - return null; -} diff --git a/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/app/user-session-check.gql b/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/app/user-session-provider.gql similarity index 89% rename from plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/app/user-session-check.gql rename to plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/app/user-session-provider.gql index 3d7fe0c11..e81271c84 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/app/user-session-check.gql +++ b/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/app/user-session-provider.gql @@ -2,5 +2,6 @@ query GetCurrentUserSession { currentUserSession { userId expiresAt + roles } } diff --git a/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/app/user-session-provider.tsx b/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/app/user-session-provider.tsx index e58e5dd52..0f643e5ff 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/app/user-session-provider.tsx +++ b/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/app/user-session-provider.tsx @@ -1,11 +1,14 @@ // @ts-nocheck -import type { UserSessionData } from '$userSessionClient'; -import type { UserSessionClientContextValue } from '$useUserSessionClient'; +import type { SessionData } from '%authHooksImports'; import type React from 'react'; -import { createUserSessionClient } from '$userSessionClient'; -import { UserSessionClientContext } from '$useUserSessionClient'; +import { userSessionClient } from '$userSessionClient'; +import { GetCurrentUserSessionDocument } from '%generatedGraphqlImports'; +import { AuthSessionContext } from '%localAuthHooksImports'; +import { ErrorableLoader } from '%reactComponentsImports'; +import { logAndFormatError } from '%reactErrorImports'; +import { useQuery } from '@apollo/client'; import { useEffect, useMemo, useState } from 'react'; interface UserSessionProviderProps { @@ -15,31 +18,57 @@ interface UserSessionProviderProps { export function UserSessionProvider({ children, }: UserSessionProviderProps): React.JSX.Element { - const [userSessionClient] = useState(() => createUserSessionClient()); - const [session, setSession] = useState( - userSessionClient.getSession(), + const [cachedUserId, setCachedUserId] = useState( + userSessionClient.getUserId(), ); - // Subscribe to session changes - useEffect( - () => - userSessionClient.onSessionChange((newSession) => { - setSession(newSession); - }), - [userSessionClient], - ); + const { + data: sessionQueryData, + error: sessionError, + refetch: refetchSession, + } = useQuery(GetCurrentUserSessionDocument, { + notifyOnNetworkStatusChange: true, + }); - const contextValue: UserSessionClientContextValue = useMemo( - () => ({ - client: userSessionClient, - session, - }), - [userSessionClient, session], - ); + const session = useMemo((): SessionData | undefined => { + if (!sessionQueryData && cachedUserId) { + // wait for server to fetch before loading session + return undefined; + } + if (!sessionQueryData?.currentUserSession) { + return { + userId: undefined, + isAuthenticated: false, + roles: ['public'], + }; + } + return { + userId: sessionQueryData.currentUserSession.userId, + isAuthenticated: true, + roles: sessionQueryData.currentUserSession.roles, + }; + }, [sessionQueryData, cachedUserId]); + + useEffect(() => { + const unsubscribe = userSessionClient.onUserIdChange((newUserId) => { + if (newUserId !== cachedUserId) { + setCachedUserId(newUserId); + refetchSession().catch((err: unknown) => { + logAndFormatError(err); + }); + } + }); + + return unsubscribe; + }, [cachedUserId, refetchSession]); + + if (!session) { + return ; + } return ( - + {children} - + ); } diff --git a/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/hooks/use-user-session-client.ts b/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/hooks/use-user-session-client.ts deleted file mode 100644 index 7191ea35e..000000000 --- a/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/hooks/use-user-session-client.ts +++ /dev/null @@ -1,30 +0,0 @@ -// @ts-nocheck - -import type { UserSessionClient, UserSessionData } from '$userSessionClient'; - -import { createContext, useContext } from 'react'; - -export interface UserSessionClientContextValue { - client: UserSessionClient; - session: UserSessionData | undefined; -} - -export const UserSessionClientContext = createContext< - UserSessionClientContextValue | undefined ->(undefined); - -/** - * Hook to get the user session client - * @returns The user session client - */ -export function useUserSessionClient(): UserSessionClientContextValue { - const client = useContext(UserSessionClientContext); - - if (!client) { - throw new Error( - 'useUserSessionClient must be used within a UserSessionClientProvider', - ); - } - - return client; -} diff --git a/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/services/user-session-client.ts b/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/services/user-session-client.ts index 43b666975..40f926551 100644 --- a/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/services/user-session-client.ts +++ b/plugins/plugin-auth/src/local-auth/core/generators/react-session/templates/src/services/user-session-client.ts @@ -3,28 +3,9 @@ import { getSafeLocalStorage } from '%reactUtilsImports'; /** - * Session data returned by the user session client + * Callback function for user ID change events */ -export interface UserSessionData { - userId: string; -} - -/** - * Callback function for session change events - */ -export type SessionChangeCallback = ( - session: UserSessionData | undefined, -) => void; - -/** - * Configuration options for creating the user session client - */ -export interface UserSessionClientConfig { - /** - * Optional initial session data - */ - initialSession?: UserSessionData; -} +type UserIdChangeCallback = (userId: string | undefined) => void; /** * User session client for managing session persistence using localStorage @@ -33,26 +14,12 @@ export interface UserSessionClientConfig { export class UserSessionClient { private static readonly USER_ID_STORAGE_KEY = 'APP_USER_ID'; private readonly storage = getSafeLocalStorage(); - private readonly callbacks = new Set(); + private readonly callbacks = new Set(); private cleanupListener?: () => void; - constructor(config?: UserSessionClientConfig) { + constructor() { // Initialize storage listener for cross-tab synchronization this.setupStorageListener(); - - // Set initial session if provided - if (config?.initialSession?.userId) { - this.setUserId(config.initialSession.userId); - } - } - - /** - * Get the current session data - * @returns Current session information - */ - getSession(): UserSessionData | undefined { - const userId = this.getUserId(); - return userId ? { userId } : undefined; } /** @@ -73,11 +40,11 @@ export class UserSessionClient { } /** - * Subscribe to session changes - * @param callback - Function to call when session changes + * Subscribe to user ID changes + * @param callback - Function to call when user ID changes * @returns Cleanup function to unsubscribe */ - onSessionChange(callback: SessionChangeCallback): () => void { + onUserIdChange(callback: UserIdChangeCallback): () => void { this.callbacks.add(callback); return () => { this.callbacks.delete(callback); @@ -96,8 +63,10 @@ export class UserSessionClient { * Get the current user ID from storage * @returns User ID or null if not authenticated */ - private getUserId(): string | null { - return this.storage.getItem(UserSessionClient.USER_ID_STORAGE_KEY); + getUserId(): string | undefined { + return ( + this.storage.getItem(UserSessionClient.USER_ID_STORAGE_KEY) ?? undefined + ); } /** @@ -134,20 +103,14 @@ export class UserSessionClient { * Notify all registered callbacks of session changes */ private notifyCallbacks(): void { - const session = this.getSession(); + const userId = this.getUserId(); for (const callback of this.callbacks) { - callback(session); + callback(userId); } } } /** - * Factory function to create a user session client - * @param config - Optional configuration for the client - * @returns New UserSessionClient instance + * Global user session client instance */ -export function createUserSessionClient( - config?: UserSessionClientConfig, -): UserSessionClient { - return new UserSessionClient(config); -} +export const userSessionClient = new UserSessionClient(); diff --git a/plugins/plugin-auth/src/placeholder-auth/core/generators/placeholder-auth-hooks/extractor.json b/plugins/plugin-auth/src/placeholder-auth/core/generators/placeholder-auth-hooks/extractor.json index d784a28ea..b0d1e9351 100644 --- a/plugins/plugin-auth/src/placeholder-auth/core/generators/placeholder-auth-hooks/extractor.json +++ b/plugins/plugin-auth/src/placeholder-auth/core/generators/placeholder-auth-hooks/extractor.json @@ -65,7 +65,8 @@ "pathRootRelativePath": "{src-root}/hooks/use-session.ts", "projectExports": { "SessionData": { "isTypeOnly": true }, - "useSession": {} + "useSession": {}, + "AuthRole": { "isTypeOnly": true } }, "sourceFile": "src/hooks/use-session.ts", "variables": {} diff --git a/plugins/plugin-auth/src/placeholder-auth/core/generators/placeholder-auth-hooks/generated/ts-import-providers.ts b/plugins/plugin-auth/src/placeholder-auth/core/generators/placeholder-auth-hooks/generated/ts-import-providers.ts index d58795727..4026ef971 100644 --- a/plugins/plugin-auth/src/placeholder-auth/core/generators/placeholder-auth-hooks/generated/ts-import-providers.ts +++ b/plugins/plugin-auth/src/placeholder-auth/core/generators/placeholder-auth-hooks/generated/ts-import-providers.ts @@ -19,6 +19,7 @@ const placeholderAuthCorePlaceholderAuthHooksImportsTask = createGeneratorTask({ return { providers: { authHooksImports: createTsImportMap(authHooksImportsSchema, { + AuthRole: paths.useSession, SessionData: paths.useSession, useCurrentUser: paths.useCurrentUser, useLogOut: paths.useLogOut, diff --git a/plugins/plugin-auth/src/placeholder-auth/core/generators/placeholder-auth-hooks/generated/typed-templates.ts b/plugins/plugin-auth/src/placeholder-auth/core/generators/placeholder-auth-hooks/generated/typed-templates.ts index 9251a36c2..180a91b9a 100644 --- a/plugins/plugin-auth/src/placeholder-auth/core/generators/placeholder-auth-hooks/generated/typed-templates.ts +++ b/plugins/plugin-auth/src/placeholder-auth/core/generators/placeholder-auth-hooks/generated/typed-templates.ts @@ -59,7 +59,11 @@ const useSession = createTsTemplateFile({ group: 'hooks', importMapProviders: {}, name: 'use-session', - projectExports: { SessionData: { isTypeOnly: true }, useSession: {} }, + projectExports: { + SessionData: { isTypeOnly: true }, + useSession: {}, + AuthRole: { isTypeOnly: true }, + }, source: { path: path.join( import.meta.dirname, diff --git a/plugins/plugin-auth/tsconfig.build.json b/plugins/plugin-auth/tsconfig.build.json index e3f281573..f3c38db96 100644 --- a/plugins/plugin-auth/tsconfig.build.json +++ b/plugins/plugin-auth/tsconfig.build.json @@ -32,6 +32,9 @@ }, { "path": "../../packages/ui-components/tsconfig.build.json" + }, + { + "path": "../../packages/utils/tsconfig.build.json" } ] } diff --git a/plugins/plugin-auth/vite.config.ts b/plugins/plugin-auth/vite.config.ts index 8a989cd64..cf2c8eeb6 100644 --- a/plugins/plugin-auth/vite.config.ts +++ b/plugins/plugin-auth/vite.config.ts @@ -42,6 +42,7 @@ export default defineConfig(async (): Promise => { '@baseplate-dev/project-builder-lib/web': { version: '*', }, + '@baseplate-dev/utils': { version: '*' }, '@baseplate-dev/ui-components': { version: '*' }, '@tanstack/react-router': { version: '*' }, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f65f4fdeb..3d4b5ee36 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1218,6 +1218,9 @@ importers: '@baseplate-dev/ui-components': specifier: workspace:* version: link:../../packages/ui-components + '@baseplate-dev/utils': + specifier: workspace:* + version: link:../../packages/utils '@hookform/lenses': specifier: 0.7.1 version: 0.7.1(react-hook-form@7.60.0(react@19.1.0))(react@19.1.0)