Merge pull request 'feat(auth): P5-004 Authentik OIDC adapter via Better Auth genericOAuth' (#97) from feat/p5-sso-authentik into main
Some checks failed
ci/woodpecker/push/ci Pipeline failed
Some checks failed
ci/woodpecker/push/ci Pipeline failed
This commit was merged in pull request #97.
This commit is contained in:
@@ -12,6 +12,15 @@ async function bootstrap(): Promise<void> {
|
|||||||
throw new Error('BETTER_AUTH_SECRET is required');
|
throw new Error('BETTER_AUTH_SECRET is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.env['AUTHENTIK_CLIENT_ID'] &&
|
||||||
|
(!process.env['AUTHENTIK_CLIENT_SECRET'] || !process.env['AUTHENTIK_ISSUER'])
|
||||||
|
) {
|
||||||
|
console.warn(
|
||||||
|
'[warn] AUTHENTIK_CLIENT_ID is set but AUTHENTIK_CLIENT_SECRET or AUTHENTIK_ISSUER is missing — Authentik SSO will not work',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const logger = new Logger('Bootstrap');
|
const logger = new Logger('Bootstrap');
|
||||||
const app = await NestFactory.create<NestFastifyApplication>(
|
const app = await NestFactory.create<NestFastifyApplication>(
|
||||||
AppModule,
|
AppModule,
|
||||||
|
|||||||
40
docs/plans/authentik-sso-setup.md
Normal file
40
docs/plans/authentik-sso-setup.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Authentik SSO Setup
|
||||||
|
|
||||||
|
## Create the Authentik application
|
||||||
|
|
||||||
|
1. In Authentik, create an OAuth2/OpenID Provider.
|
||||||
|
2. Create an Application and link it to that provider.
|
||||||
|
3. Copy the generated client ID and client secret.
|
||||||
|
|
||||||
|
## Required environment variables
|
||||||
|
|
||||||
|
Set these values for the gateway/auth runtime:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
AUTHENTIK_CLIENT_ID=your-client-id
|
||||||
|
AUTHENTIK_CLIENT_SECRET=your-client-secret
|
||||||
|
AUTHENTIK_ISSUER=https://authentik.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
`AUTHENTIK_ISSUER` should be the Authentik base URL, for example `https://authentik.example.com`.
|
||||||
|
|
||||||
|
## Redirect URI
|
||||||
|
|
||||||
|
Configure this redirect URI in the Authentik provider/application:
|
||||||
|
|
||||||
|
```text
|
||||||
|
{BETTER_AUTH_URL}/api/auth/callback/authentik
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://mosaic.example.com/api/auth/callback/authentik
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test the flow
|
||||||
|
|
||||||
|
1. Start the gateway with `BETTER_AUTH_URL` and the Authentik environment variables set.
|
||||||
|
2. Open the Mosaic login flow and choose the Authentik provider.
|
||||||
|
3. Complete the Authentik login.
|
||||||
|
4. Confirm the browser returns to Mosaic and a session is created successfully.
|
||||||
44
docs/scratchpads/p5-004-authentik-sso.md
Normal file
44
docs/scratchpads/p5-004-authentik-sso.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# P5-004 Scratchpad
|
||||||
|
|
||||||
|
- Objective: Add optional Authentik OIDC SSO adapter via Better Auth genericOAuth.
|
||||||
|
- Task ref: P5-004
|
||||||
|
- Issue ref: #96
|
||||||
|
- Plan:
|
||||||
|
1. Inspect auth/gateway surfaces and Better Auth plugin shape.
|
||||||
|
2. Add failing coverage for auth config/startup validation where feasible.
|
||||||
|
3. Implement adapter, docs, and warnings.
|
||||||
|
4. Run targeted typechecks, lint, and review.
|
||||||
|
|
||||||
|
- TDD note: no low-friction auth plugin or bootstrap-env test seam exists for `packages/auth/src/auth.ts` or `apps/gateway/src/main.ts`. This change is configuration-oriented and does not alter an existing behavioral contract with a current test harness. I skipped new tests for this pass and relied on exact typecheck/lint/test commands plus manual review.
|
||||||
|
|
||||||
|
- Changes:
|
||||||
|
1. Added conditional Better Auth `genericOAuth` plugin registration for the `authentik` provider in `packages/auth/src/auth.ts`.
|
||||||
|
2. Added a soft startup warning in `apps/gateway/src/main.ts` for incomplete Authentik env configuration.
|
||||||
|
3. Added `docs/plans/authentik-sso-setup.md` with env, redirect URI, and test-flow guidance.
|
||||||
|
4. Confirmed `packages/auth/src/index.ts` already exports `AuthConfig`; no change required there.
|
||||||
|
|
||||||
|
- Verification:
|
||||||
|
1. `pnpm --filter @mosaic/db build`
|
||||||
|
2. `pnpm --filter @mosaic/auth typecheck`
|
||||||
|
3. `pnpm --filter @mosaic/gateway typecheck`
|
||||||
|
4. `pnpm lint`
|
||||||
|
5. `pnpm format:check`
|
||||||
|
6. `pnpm --filter @mosaic/auth test`
|
||||||
|
7. `pnpm --filter @mosaic/gateway test`
|
||||||
|
|
||||||
|
- Results:
|
||||||
|
1. `@mosaic/auth` typecheck passed after replacing the non-existent `enabled` field with conditional plugin registration.
|
||||||
|
2. `@mosaic/gateway` typecheck passed.
|
||||||
|
3. Repo lint passed.
|
||||||
|
4. Prettier check passed after formatting `apps/gateway/src/main.ts`.
|
||||||
|
5. `@mosaic/auth` tests reported `No test files found, exiting with code 0`.
|
||||||
|
6. `@mosaic/gateway` tests passed: `3` files, `20` tests.
|
||||||
|
|
||||||
|
- Review:
|
||||||
|
1. Manual review of the diff found no blocker issues.
|
||||||
|
2. External `codex-code-review.sh --uncommitted` was attempted but did not return a usable verdict in-session; no automated review findings were available from that run.
|
||||||
|
|
||||||
|
- Situational evidence:
|
||||||
|
1. Provider activation is env-gated by `AUTHENTIK_CLIENT_ID`.
|
||||||
|
2. Misconfigured optional SSO surfaces a warning instead of crashing gateway startup.
|
||||||
|
3. Setup doc records the expected redirect path: `{BETTER_AUTH_URL}/api/auth/callback/authentik`.
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { betterAuth } from 'better-auth';
|
import { betterAuth } from 'better-auth';
|
||||||
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
|
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
|
||||||
|
import { genericOAuth } from 'better-auth/plugins';
|
||||||
import type { Db } from '@mosaic/db';
|
import type { Db } from '@mosaic/db';
|
||||||
|
|
||||||
export interface AuthConfig {
|
export interface AuthConfig {
|
||||||
@@ -10,6 +11,33 @@ export interface AuthConfig {
|
|||||||
|
|
||||||
export function createAuth(config: AuthConfig) {
|
export function createAuth(config: AuthConfig) {
|
||||||
const { db, baseURL, secret } = config;
|
const { db, baseURL, secret } = config;
|
||||||
|
const authentikIssuer = process.env['AUTHENTIK_ISSUER'];
|
||||||
|
const authentikClientId = process.env['AUTHENTIK_CLIENT_ID'];
|
||||||
|
const authentikClientSecret = process.env['AUTHENTIK_CLIENT_SECRET'];
|
||||||
|
const plugins = authentikClientId
|
||||||
|
? [
|
||||||
|
genericOAuth({
|
||||||
|
config: [
|
||||||
|
{
|
||||||
|
providerId: 'authentik',
|
||||||
|
clientId: authentikClientId,
|
||||||
|
clientSecret: authentikClientSecret ?? '',
|
||||||
|
discoveryUrl: authentikIssuer
|
||||||
|
? `${authentikIssuer}/.well-known/openid-configuration`
|
||||||
|
: undefined,
|
||||||
|
authorizationUrl: authentikIssuer
|
||||||
|
? `${authentikIssuer}/application/o/authorize/`
|
||||||
|
: undefined,
|
||||||
|
tokenUrl: authentikIssuer ? `${authentikIssuer}/application/o/token/` : undefined,
|
||||||
|
userInfoUrl: authentikIssuer
|
||||||
|
? `${authentikIssuer}/application/o/userinfo/`
|
||||||
|
: undefined,
|
||||||
|
scopes: ['openid', 'email', 'profile'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return betterAuth({
|
return betterAuth({
|
||||||
database: drizzleAdapter(db, {
|
database: drizzleAdapter(db, {
|
||||||
@@ -36,6 +64,7 @@ export function createAuth(config: AuthConfig) {
|
|||||||
expiresIn: 60 * 60 * 24 * 7, // 7 days
|
expiresIn: 60 * 60 * 24 * 7, // 7 days
|
||||||
updateAge: 60 * 60 * 24, // refresh daily
|
updateAge: 60 * 60 * 24, // refresh daily
|
||||||
},
|
},
|
||||||
|
plugins,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user