@adonis-agora/authkit-server 0.53.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.d.ts CHANGED
@@ -136,3 +136,5 @@ export { completeSudo, fail as failSudo, LAST_METHOD_SESSION_KEY, } from './src/
136
136
  export { sudoContextFrom } from './src/host/sudo/runtime.js';
137
137
  export type { SudoMethod, SudoContext, SudoMethodDescriptor, SudoRouteHelpers, } from './src/host/sudo/types.js';
138
138
  export { authkitUserProvider } from './src/host/adonis_auth_user_provider.js';
139
+ export { OidcRpGuard, oidcRpGuard } from './src/host/oidc_rp_guard.js';
140
+ export type { OidcRpGuardOptions, OidcRpGuardEvents } from './src/host/oidc_rp_guard.js';
package/build/index.js CHANGED
@@ -112,3 +112,8 @@ export { sudoContextFrom } from './src/host/sudo/runtime.js';
112
112
  // `adonisAuth: { guard: '...' }` in config/authkit.ts so the account console's
113
113
  // login/logout also sync ctx.auth. See src/host/adonis_auth_user_provider.ts.
114
114
  export { authkitUserProvider } from './src/host/adonis_auth_user_provider.js';
115
+ // @adonisjs/auth integration (opt-in) — guard pra Relying Parties OIDC.
116
+ // O RP não autentica ninguém (sem senha); a identidade vem da sessão gravada
117
+ // pelo callback OIDC. Plugado em config/auth.ts, faz ctx.auth.user funcionar
118
+ // nativamente em qualquer app que consome o issuer via Authorization Code + PKCE.
119
+ export { OidcRpGuard, oidcRpGuard } from './src/host/oidc_rp_guard.js';
@@ -0,0 +1,89 @@
1
+ import { symbols } from '@adonisjs/auth';
2
+ import type { AuthClientResponse, GuardConfigProvider, GuardContract } from '@adonisjs/auth/types';
3
+ import type { SessionUserProviderContract } from '@adonisjs/auth/types/session';
4
+ import type { HttpContext } from '@adonisjs/core/http';
5
+ import type { ConfigProvider } from '@adonisjs/core/types';
6
+ import type { EmitterLike } from '@adonisjs/core/types/events';
7
+ type RealUser<UserProvider> = UserProvider extends SessionUserProviderContract<infer U> ? U : never;
8
+ export type OidcRpGuardOptions<UserProvider extends SessionUserProviderContract<unknown>> = {
9
+ provider: UserProvider | ConfigProvider<UserProvider>;
10
+ sessionKey?: string;
11
+ };
12
+ export type OidcRpGuardEvents<User> = {
13
+ 'oidc_rp:login_succeeded': {
14
+ ctx: HttpContext;
15
+ guardName: string;
16
+ user: User;
17
+ };
18
+ 'oidc_rp:authentication_succeeded': {
19
+ ctx: HttpContext;
20
+ guardName: string;
21
+ user: User;
22
+ };
23
+ 'oidc_rp:authentication_failed': {
24
+ ctx: HttpContext;
25
+ guardName: string;
26
+ };
27
+ 'oidc_rp:logged_out': {
28
+ ctx: HttpContext;
29
+ guardName: string;
30
+ user: User | null;
31
+ };
32
+ };
33
+ /**
34
+ * Guard de `@adonisjs/auth` pra Relying Parties OIDC — o app não autentica
35
+ * ninguém (sem senha, sem remember-me); a identidade vem da sessão gravada
36
+ * pelo callback OIDC (`account_user_id`). O guard só LÊ essa chave e resolve
37
+ * o user via provider.
38
+ *
39
+ * ```ts
40
+ * // config/auth.ts
41
+ * import { oidcRpGuard } from '@adonis-agora/authkit-server'
42
+ * import { sessionUserProvider } from '@adonisjs/auth/session'
43
+ *
44
+ * const authConfig = defineConfig({
45
+ * default: 'web',
46
+ * guards: {
47
+ * web: oidcRpGuard({
48
+ * provider: sessionUserProvider({ model: () => import('#models/user') }),
49
+ * }),
50
+ * },
51
+ * })
52
+ * ```
53
+ *
54
+ * O callback OIDC do RP chama `ctx.auth.use('web').login(user)` pra gravar a
55
+ * sessão; a partir daí `ctx.auth.user`, `auth.check()`, `middleware.auth()` —
56
+ * tudo funciona nativamente.
57
+ */
58
+ export declare class OidcRpGuard<UserProvider extends SessionUserProviderContract<unknown>> implements GuardContract<RealUser<UserProvider>> {
59
+ #private;
60
+ [symbols.GUARD_KNOWN_EVENTS]: OidcRpGuardEvents<RealUser<UserProvider>>;
61
+ driverName: "oidc_rp";
62
+ authenticationAttempted: boolean;
63
+ isAuthenticated: boolean;
64
+ isLoggedOut: boolean;
65
+ user?: RealUser<UserProvider>;
66
+ constructor(name: string, ctx: HttpContext, sessionKey: string, emitter: EmitterLike<OidcRpGuardEvents<RealUser<UserProvider>>>, userProvider: UserProvider);
67
+ getUserOrFail(): RealUser<UserProvider>;
68
+ /**
69
+ * Grava a identidade na sessão. Chamado pelo callback OIDC após validar o
70
+ * grant — o guard NÃO verifica credenciais, só persiste o id do user.
71
+ */
72
+ login(user: RealUser<UserProvider>): Promise<void>;
73
+ /**
74
+ * Limpa a identidade da sessão. O RP-initiated logout (redirect pro
75
+ * `end_session` do issuer) é responsabilidade do controller — o guard só
76
+ * cuida da sessão local.
77
+ */
78
+ logout(): Promise<void>;
79
+ authenticate(): Promise<RealUser<UserProvider>>;
80
+ check(): Promise<boolean>;
81
+ authenticateAsClient(user: RealUser<UserProvider>): Promise<AuthClientResponse>;
82
+ }
83
+ /**
84
+ * Factory de config pro `oidcRpGuard` — mesmo padrão do `sessionGuard()` de
85
+ * `@adonisjs/auth/session`. Retorna um `GuardConfigProvider` que o
86
+ * `defineConfig` de `config/auth.ts` resolve no boot.
87
+ */
88
+ export declare function oidcRpGuard<UserProvider extends SessionUserProviderContract<unknown>>(config: OidcRpGuardOptions<UserProvider>): GuardConfigProvider<(ctx: HttpContext) => OidcRpGuard<UserProvider>>;
89
+ export {};
@@ -0,0 +1,154 @@
1
+ import { symbols } from '@adonisjs/auth';
2
+ import { RuntimeException } from '@adonisjs/core/exceptions';
3
+ import { ACCOUNT_SESSION_KEY } from './middleware/account_auth.js';
4
+ /**
5
+ * Guard de `@adonisjs/auth` pra Relying Parties OIDC — o app não autentica
6
+ * ninguém (sem senha, sem remember-me); a identidade vem da sessão gravada
7
+ * pelo callback OIDC (`account_user_id`). O guard só LÊ essa chave e resolve
8
+ * o user via provider.
9
+ *
10
+ * ```ts
11
+ * // config/auth.ts
12
+ * import { oidcRpGuard } from '@adonis-agora/authkit-server'
13
+ * import { sessionUserProvider } from '@adonisjs/auth/session'
14
+ *
15
+ * const authConfig = defineConfig({
16
+ * default: 'web',
17
+ * guards: {
18
+ * web: oidcRpGuard({
19
+ * provider: sessionUserProvider({ model: () => import('#models/user') }),
20
+ * }),
21
+ * },
22
+ * })
23
+ * ```
24
+ *
25
+ * O callback OIDC do RP chama `ctx.auth.use('web').login(user)` pra gravar a
26
+ * sessão; a partir daí `ctx.auth.user`, `auth.check()`, `middleware.auth()` —
27
+ * tudo funciona nativamente.
28
+ */
29
+ export class OidcRpGuard {
30
+ #name;
31
+ #ctx;
32
+ #sessionKey;
33
+ #emitter;
34
+ #userProvider;
35
+ [symbols.GUARD_KNOWN_EVENTS] = {};
36
+ driverName = 'oidc_rp';
37
+ authenticationAttempted = false;
38
+ isAuthenticated = false;
39
+ isLoggedOut = false;
40
+ user;
41
+ constructor(name, ctx, sessionKey, emitter, userProvider) {
42
+ this.#name = name;
43
+ this.#ctx = ctx;
44
+ this.#sessionKey = sessionKey;
45
+ this.#emitter = emitter;
46
+ this.#userProvider = userProvider;
47
+ }
48
+ getUserOrFail() {
49
+ if (!this.user) {
50
+ throw new RuntimeException('Cannot access user. Authentication has not been attempted or failed.');
51
+ }
52
+ return this.user;
53
+ }
54
+ /**
55
+ * Grava a identidade na sessão. Chamado pelo callback OIDC após validar o
56
+ * grant — o guard NÃO verifica credenciais, só persiste o id do user.
57
+ */
58
+ async login(user) {
59
+ const guardUser = await this.#userProvider.createUserForGuard(user);
60
+ this.#ctx.session.put(this.#sessionKey, String(guardUser.getId()));
61
+ this.user = user;
62
+ this.isAuthenticated = true;
63
+ this.authenticationAttempted = true;
64
+ this.#emitter.emit('oidc_rp:login_succeeded', {
65
+ ctx: this.#ctx,
66
+ guardName: this.#name,
67
+ user,
68
+ });
69
+ }
70
+ /**
71
+ * Limpa a identidade da sessão. O RP-initiated logout (redirect pro
72
+ * `end_session` do issuer) é responsabilidade do controller — o guard só
73
+ * cuida da sessão local.
74
+ */
75
+ async logout() {
76
+ const user = this.user ?? null;
77
+ this.#ctx.session.forget(this.#sessionKey);
78
+ this.user = undefined;
79
+ this.isAuthenticated = false;
80
+ this.isLoggedOut = true;
81
+ this.#emitter.emit('oidc_rp:logged_out', {
82
+ ctx: this.#ctx,
83
+ guardName: this.#name,
84
+ user,
85
+ });
86
+ }
87
+ async authenticate() {
88
+ if (this.authenticationAttempted) {
89
+ return this.getUserOrFail();
90
+ }
91
+ this.authenticationAttempted = true;
92
+ const userId = this.#ctx.session.get(this.#sessionKey);
93
+ if (!userId) {
94
+ this.#emitter.emit('oidc_rp:authentication_failed', {
95
+ ctx: this.#ctx,
96
+ guardName: this.#name,
97
+ });
98
+ throw new RuntimeException('Unauthorized', { cause: 'E_UNAUTHORIZED_ACCESS' });
99
+ }
100
+ const guardUser = await this.#userProvider.findById(userId);
101
+ if (!guardUser) {
102
+ this.#ctx.session.forget(this.#sessionKey);
103
+ this.#emitter.emit('oidc_rp:authentication_failed', {
104
+ ctx: this.#ctx,
105
+ guardName: this.#name,
106
+ });
107
+ throw new RuntimeException('Unauthorized', { cause: 'E_UNAUTHORIZED_ACCESS' });
108
+ }
109
+ this.user = guardUser.getOriginal();
110
+ this.isAuthenticated = true;
111
+ this.#emitter.emit('oidc_rp:authentication_succeeded', {
112
+ ctx: this.#ctx,
113
+ guardName: this.#name,
114
+ user: this.user,
115
+ });
116
+ return this.user;
117
+ }
118
+ async check() {
119
+ try {
120
+ await this.authenticate();
121
+ return true;
122
+ }
123
+ catch {
124
+ return false;
125
+ }
126
+ }
127
+ async authenticateAsClient(user) {
128
+ const guardUser = await this.#userProvider.createUserForGuard(user);
129
+ return { session: { [this.#sessionKey]: String(guardUser.getId()) } };
130
+ }
131
+ }
132
+ /**
133
+ * Factory de config pro `oidcRpGuard` — mesmo padrão do `sessionGuard()` de
134
+ * `@adonisjs/auth/session`. Retorna um `GuardConfigProvider` que o
135
+ * `defineConfig` de `config/auth.ts` resolve no boot.
136
+ */
137
+ export function oidcRpGuard(config) {
138
+ return {
139
+ async resolver(name, app) {
140
+ const emitter = await app.container.make('emitter');
141
+ const sessionKey = config.sessionKey ?? ACCOUNT_SESSION_KEY;
142
+ let userProvider;
143
+ if (typeof config.provider.resolver === 'function') {
144
+ userProvider = await config.provider.resolver(app);
145
+ }
146
+ else {
147
+ userProvider = config.provider;
148
+ }
149
+ return (ctx) => {
150
+ return new OidcRpGuard(name, ctx, sessionKey, emitter, userProvider);
151
+ };
152
+ },
153
+ };
154
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-server",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "description": "AdonisJS OIDC/OAuth2 provider (Identity Provider) toolkit: ejectable auth server with sessions, rate-limiting, MFA/TOTP, audit log, federated logout and OpenTelemetry metrics.",
5
5
  "license": "MIT",
6
6
  "author": "dudousxd",