@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 +2 -0
- package/build/index.js +5 -0
- package/build/src/host/oidc_rp_guard.d.ts +89 -0
- package/build/src/host/oidc_rp_guard.js +154 -0
- package/package.json +1 -1
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.
|
|
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",
|