@c9up/warden 0.1.17 → 0.1.19
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/dist/AuthManager.d.ts +86 -1
- package/dist/AuthManager.d.ts.map +1 -1
- package/dist/AuthManager.js +113 -10
- package/dist/AuthManager.js.map +1 -1
- package/dist/Authenticator.d.ts +13 -1
- package/dist/Authenticator.d.ts.map +1 -1
- package/dist/Authenticator.js +37 -3
- package/dist/Authenticator.js.map +1 -1
- package/dist/RememberMeToken.d.ts +169 -0
- package/dist/RememberMeToken.d.ts.map +1 -0
- package/dist/RememberMeToken.js +269 -0
- package/dist/RememberMeToken.js.map +1 -0
- package/dist/ResilientBlacklistDriver.d.ts +15 -9
- package/dist/ResilientBlacklistDriver.d.ts.map +1 -1
- package/dist/ResilientBlacklistDriver.js +53 -15
- package/dist/ResilientBlacklistDriver.js.map +1 -1
- package/dist/Secret.d.ts +11 -0
- package/dist/Secret.d.ts.map +1 -0
- package/dist/Secret.js +36 -0
- package/dist/Secret.js.map +1 -0
- package/dist/WardenProvider.d.ts +1 -0
- package/dist/WardenProvider.d.ts.map +1 -1
- package/dist/WardenProvider.js +64 -0
- package/dist/WardenProvider.js.map +1 -1
- package/dist/bouncer/Bouncer.d.ts +46 -1
- package/dist/bouncer/Bouncer.d.ts.map +1 -1
- package/dist/bouncer/Bouncer.js +77 -4
- package/dist/bouncer/Bouncer.js.map +1 -1
- package/dist/bouncer/PolicyAuthorizer.d.ts +9 -1
- package/dist/bouncer/PolicyAuthorizer.d.ts.map +1 -1
- package/dist/bouncer/PolicyAuthorizer.js +17 -1
- package/dist/bouncer/PolicyAuthorizer.js.map +1 -1
- package/dist/bouncer/evaluate.d.ts +14 -2
- package/dist/bouncer/evaluate.d.ts.map +1 -1
- package/dist/bouncer/evaluate.js +16 -5
- package/dist/bouncer/evaluate.js.map +1 -1
- package/dist/bouncer/types.d.ts +4 -2
- package/dist/bouncer/types.d.ts.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -1
- package/dist/firstcontact/drivers/GitHubDriver.d.ts.map +1 -1
- package/dist/firstcontact/drivers/GitHubDriver.js +2 -3
- package/dist/firstcontact/drivers/GitHubDriver.js.map +1 -1
- package/dist/firstcontact/drivers/GoogleDriver.d.ts.map +1 -1
- package/dist/firstcontact/drivers/GoogleDriver.js +2 -3
- package/dist/firstcontact/drivers/GoogleDriver.js.map +1 -1
- package/dist/firstcontact/types.d.ts +10 -0
- package/dist/firstcontact/types.d.ts.map +1 -1
- package/dist/firstcontact/types.js +18 -1
- package/dist/firstcontact/types.js.map +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/mfa/MfaManager.d.ts.map +1 -1
- package/dist/mfa/MfaManager.js +14 -5
- package/dist/mfa/MfaManager.js.map +1 -1
- package/dist/mfa/TotpProvider.d.ts +34 -1
- package/dist/mfa/TotpProvider.d.ts.map +1 -1
- package/dist/mfa/TotpProvider.js +60 -4
- package/dist/mfa/TotpProvider.js.map +1 -1
- package/dist/mfa/webauthn-codec.d.ts +6 -1
- package/dist/mfa/webauthn-codec.d.ts.map +1 -1
- package/dist/mfa/webauthn-codec.js +50 -6
- package/dist/mfa/webauthn-codec.js.map +1 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +46 -1
- package/dist/middleware.js.map +1 -1
- package/dist/quasar.d.ts.map +1 -1
- package/dist/quasar.js +6 -14
- package/dist/quasar.js.map +1 -1
- package/dist/sanitize.d.ts +10 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +15 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/strategies/ApiKeyStrategy.d.ts +10 -1
- package/dist/strategies/ApiKeyStrategy.d.ts.map +1 -1
- package/dist/strategies/ApiKeyStrategy.js +16 -0
- package/dist/strategies/ApiKeyStrategy.js.map +1 -1
- package/dist/strategies/BasicAuthStrategy.d.ts +59 -0
- package/dist/strategies/BasicAuthStrategy.d.ts.map +1 -0
- package/dist/strategies/BasicAuthStrategy.js +97 -0
- package/dist/strategies/BasicAuthStrategy.js.map +1 -0
- package/dist/strategies/JwtStrategy.d.ts +10 -1
- package/dist/strategies/JwtStrategy.d.ts.map +1 -1
- package/dist/strategies/JwtStrategy.js +11 -0
- package/dist/strategies/JwtStrategy.js.map +1 -1
- package/dist/strategies/SessionStrategy.d.ts +84 -2
- package/dist/strategies/SessionStrategy.d.ts.map +1 -1
- package/dist/strategies/SessionStrategy.js +124 -1
- package/dist/strategies/SessionStrategy.js.map +1 -1
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +4 -2
- package/src/AuthManager.ts +163 -7
- package/src/Authenticator.ts +58 -10
- package/src/RememberMeToken.ts +357 -0
- package/src/ResilientBlacklistDriver.ts +58 -18
- package/src/Secret.ts +41 -0
- package/src/WardenProvider.ts +90 -0
- package/src/bouncer/Bouncer.ts +93 -4
- package/src/bouncer/PolicyAuthorizer.ts +20 -1
- package/src/bouncer/evaluate.ts +26 -6
- package/src/bouncer/types.ts +4 -2
- package/src/config.ts +9 -0
- package/src/firstcontact/drivers/GitHubDriver.ts +2 -3
- package/src/firstcontact/drivers/GoogleDriver.ts +2 -3
- package/src/firstcontact/types.ts +24 -0
- package/src/index.ts +30 -2
- package/src/mfa/MfaManager.ts +15 -8
- package/src/mfa/TotpProvider.ts +90 -6
- package/src/mfa/webauthn-codec.ts +48 -6
- package/src/middleware.ts +55 -2
- package/src/quasar.ts +6 -14
- package/src/sanitize.ts +17 -0
- package/src/strategies/ApiKeyStrategy.ts +23 -1
- package/src/strategies/BasicAuthStrategy.ts +127 -0
- package/src/strategies/JwtStrategy.ts +18 -1
- package/src/strategies/SessionStrategy.ts +167 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JwtStrategy.d.ts","sourceRoot":"","sources":["../../src/strategies/JwtStrategy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"JwtStrategy.d.ts","sourceRoot":"","sources":["../../src/strategies/JwtStrategy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACX,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,WAAW,EACX,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,wFAAwF;IACxF,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AA6BD,MAAM,WAAW,iBAAiB;IACjC;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;OAOG;IACH,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACzE,iBAAiB,EAAE,CAClB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,KACZ,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACjC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,qBAAa,WAAY,YAAW,YAAY;;IAC/C,IAAI,SAAS;gBAaD,MAAM,EAAE,iBAAiB;IAgC/B,YAAY,CACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,CAAC,UAAU,CAAC;IAkBhB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAmChD,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM;IAepC;;;;;;;;OAQG;IACH;;;;;;;OAOG;IACH,oBAAoB,CAAC,IAAI,EAAE,WAAW,GAAG,kBAAkB;IAIrD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAa7C;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
|
|
@@ -133,6 +133,17 @@ export class JwtStrategy {
|
|
|
133
133
|
* Returns `true` when the token was added to the blacklist, `false` when
|
|
134
134
|
* the token is already expired (revocation is unnecessary) or unparseable.
|
|
135
135
|
*/
|
|
136
|
+
/**
|
|
137
|
+
* The header a test client sends to be `user` (AdonisJS
|
|
138
|
+
* `authenticateAsClient`).
|
|
139
|
+
*
|
|
140
|
+
* Signs a real token with the configured secret, so the request travels the
|
|
141
|
+
* SAME verification path as production — a test that forges its own header
|
|
142
|
+
* proves only that the forgery works.
|
|
143
|
+
*/
|
|
144
|
+
authenticateAsClient(user) {
|
|
145
|
+
return { headers: { authorization: `Bearer ${this.signToken(user)}` } };
|
|
146
|
+
}
|
|
136
147
|
async revoke(token) {
|
|
137
148
|
if (!this.#blacklist) {
|
|
138
149
|
throw new Error("JwtStrategy.revoke() requires a `blacklist` driver. Pass `{ blacklist: new TokenBlacklist(driver) }` to the constructor.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JwtStrategy.js","sourceRoot":"","sources":["../../src/strategies/JwtStrategy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"JwtStrategy.js","sourceRoot":"","sources":["../../src/strategies/JwtStrategy.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @implements MISS-7
|
|
8
8
|
*/
|
|
9
|
-
import type { AuthResult, AuthStrategy, UserPayload } from "../AuthManager.js";
|
|
9
|
+
import type { AuthClientResponse, AuthResult, AuthStrategy, UserPayload } from "../AuthManager.js";
|
|
10
|
+
import { type RememberMeTokenDriver } from "../RememberMeToken.js";
|
|
10
11
|
export interface SessionStore {
|
|
11
12
|
get(key: string): unknown;
|
|
12
13
|
/**
|
|
@@ -29,11 +30,79 @@ export interface SessionStore {
|
|
|
29
30
|
export interface SessionStrategyConfig {
|
|
30
31
|
sessionKey?: string;
|
|
31
32
|
findUser: (id: string | number) => Promise<UserPayload | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Turn on "keep me signed in". Adonis calls the equivalent flag
|
|
35
|
+
* `useRememberMeTokens`; without a driver there is nowhere to persist the
|
|
36
|
+
* token, so passing one IS the opt-in.
|
|
37
|
+
*/
|
|
38
|
+
rememberMeTokens?: RememberMeTokenDriver;
|
|
39
|
+
/**
|
|
40
|
+
* How long a remember-me token lives. Adonis defaults to two years, and a
|
|
41
|
+
* number here is SECONDS (`durationToSeconds` semantics).
|
|
42
|
+
*/
|
|
43
|
+
rememberMeAge?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Cookie name holding the token. Adonis derives `remember_<guard>`; the
|
|
46
|
+
* default matches the `web` guard an app is most likely migrating.
|
|
47
|
+
*/
|
|
48
|
+
rememberMeCookieName?: string;
|
|
32
49
|
}
|
|
33
50
|
export declare class SessionStrategy implements AuthStrategy {
|
|
34
51
|
#private;
|
|
35
52
|
name: string;
|
|
36
53
|
constructor(config: SessionStrategyConfig);
|
|
54
|
+
/** The cookie the remember-me token travels in. */
|
|
55
|
+
get rememberMeCookieName(): string;
|
|
56
|
+
/** Whether "keep me signed in" is wired at all. */
|
|
57
|
+
get usesRememberMeTokens(): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the current user was revived from a remember-me cookie rather
|
|
60
|
+
* than signing in (AdonisJS `viaRemember`).
|
|
61
|
+
*
|
|
62
|
+
* This is the distinction that lets an app demand the password again before
|
|
63
|
+
* something sensitive — changing an email, spending money, deleting an
|
|
64
|
+
* account. Nothing reported it, so a session restored from a cookie looked
|
|
65
|
+
* exactly like one where the user had just typed their password.
|
|
66
|
+
*/
|
|
67
|
+
get viaRemember(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Whether a remember-me token was even tried on this request (AdonisJS
|
|
70
|
+
* `attemptedViaRemember`) — true whether or not it worked.
|
|
71
|
+
*/
|
|
72
|
+
get attemptedViaRemember(): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Whether `logout()` ran on this guard (AdonisJS `isLoggedOut`).
|
|
75
|
+
*
|
|
76
|
+
* A handler that logs out and then keeps working — clearing a cart, writing
|
|
77
|
+
* an audit line — could not tell that the session was already gone.
|
|
78
|
+
*/
|
|
79
|
+
get isLoggedOut(): boolean;
|
|
80
|
+
/** The session key the user id is stored under (AdonisJS `sessionKeyName`). */
|
|
81
|
+
get sessionKeyName(): string;
|
|
82
|
+
/** The cookie key the remember-me token is stored under (AdonisJS `rememberMeKeyName`). */
|
|
83
|
+
get rememberMeKeyName(): string;
|
|
84
|
+
/**
|
|
85
|
+
* Mint a remember-me token for `user` and return the cookie value the caller
|
|
86
|
+
* must set. `null` when the feature is not wired.
|
|
87
|
+
*/
|
|
88
|
+
issueRememberMeToken(user: UserPayload): Promise<string | null>;
|
|
89
|
+
/**
|
|
90
|
+
* Authenticate from a remember-me cookie, and RECYCLE it: the returned
|
|
91
|
+
* `cookieValue` must replace the one the browser holds.
|
|
92
|
+
*
|
|
93
|
+
* The user is re-read through `findUser`, never trusted from the token — a
|
|
94
|
+
* token outlives the row it points at, and a deleted or disabled account
|
|
95
|
+
* must not walk back in through a cookie.
|
|
96
|
+
*/
|
|
97
|
+
authenticateViaRememberMeToken(cookieValue: unknown): Promise<{
|
|
98
|
+
user: UserPayload;
|
|
99
|
+
cookieValue: string;
|
|
100
|
+
} | null>;
|
|
101
|
+
/**
|
|
102
|
+
* Drop the token behind `cookieValue`. Called on logout so the persistent
|
|
103
|
+
* credential dies with the session — Adonis deletes the row too.
|
|
104
|
+
*/
|
|
105
|
+
revokeRememberMeToken(cookieValue: unknown): Promise<void>;
|
|
37
106
|
/** Authenticate via email/password — stores user ID in session. */
|
|
38
107
|
authenticate(_credentials: {
|
|
39
108
|
email: string;
|
|
@@ -58,7 +127,20 @@ export declare class SessionStrategy implements AuthStrategy {
|
|
|
58
127
|
* path — see `wasRegenerated()` there.
|
|
59
128
|
*/
|
|
60
129
|
login(user: UserPayload, session: SessionStore): Promise<void>;
|
|
61
|
-
/**
|
|
130
|
+
/**
|
|
131
|
+
* The session entry a test client needs to be `user` (AdonisJS
|
|
132
|
+
* `authenticateAsClient`).
|
|
133
|
+
*
|
|
134
|
+
* Returns the same key `login()` writes, so the request is authenticated by
|
|
135
|
+
* the guard's own logic rather than by the test knowing where the id lives.
|
|
136
|
+
*/
|
|
137
|
+
authenticateAsClient(user: UserPayload): AuthClientResponse;
|
|
138
|
+
/**
|
|
139
|
+
* Logout — remove the user id from the session.
|
|
140
|
+
*
|
|
141
|
+
* The remember-me token is revoked separately through
|
|
142
|
+
* {@link revokeRememberMeToken}, because only the caller holds the cookie.
|
|
143
|
+
*/
|
|
62
144
|
logout(session: SessionStore): Promise<void>;
|
|
63
145
|
}
|
|
64
146
|
//# sourceMappingURL=SessionStrategy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionStrategy.d.ts","sourceRoot":"","sources":["../../src/strategies/SessionStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"SessionStrategy.d.ts","sourceRoot":"","sources":["../../src/strategies/SessionStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACX,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,WAAW,EACX,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAGN,KAAK,qBAAqB,EAE1B,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,YAAY;IAC5B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;;OAMG;IACH,UAAU,IAAI,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC/D;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;IACzC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAKD,qBAAa,eAAgB,YAAW,YAAY;;IACnD,IAAI,SAAa;gBAOL,MAAM,EAAE,qBAAqB;IAKzC,mDAAmD;IACnD,IAAI,oBAAoB,IAAI,MAAM,CAEjC;IAED,mDAAmD;IACnD,IAAI,oBAAoB,IAAI,OAAO,CAElC;IAED;;;;;;;;OAQG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;OAGG;IACH,IAAI,oBAAoB,IAAI,OAAO,CAElC;IAED;;;;;OAKG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,+EAA+E;IAC/E,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,2FAA2F;IAC3F,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAMD;;;OAGG;IACG,oBAAoB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAQrE;;;;;;;OAOG;IACG,8BAA8B,CACnC,WAAW,EAAE,OAAO,GAClB,OAAO,CAAC;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAmB7D;;;OAGG;IACG,qBAAqB,CAAC,WAAW,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAQhE,mEAAmE;IAC7D,YAAY,CACjB,YAAY,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EACjD,QAAQ,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,YAAY,CAAA;KAAE,GACnC,OAAO,CAAC,UAAU,CAAC;IAgBtB,8DAA8D;IACxD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAQjD,2EAA2E;IACrE,iBAAiB,CACtB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,YAAY,CAAA;KAAE,GAClC,OAAO,CAAC,UAAU,CAAC;IAWtB;;;;;;;;;OASG;IACG,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAUpE;;;;;;OAMG;IACH,oBAAoB,CAAC,IAAI,EAAE,WAAW,GAAG,kBAAkB;IAI3D;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAMlD"}
|
|
@@ -7,14 +7,114 @@
|
|
|
7
7
|
* @implements MISS-7
|
|
8
8
|
*/
|
|
9
9
|
import { WardenError } from "../errors.js";
|
|
10
|
+
import { decodeTokenValue, mintRememberMeToken, verifyAndRecycleRememberMeToken, } from "../RememberMeToken.js";
|
|
11
|
+
/** Two years, Adonis' `rememberMeTokensAge` default. */
|
|
12
|
+
const DEFAULT_REMEMBER_AGE_SECONDS = 63_072_000;
|
|
10
13
|
export class SessionStrategy {
|
|
11
14
|
name = "session";
|
|
12
15
|
#config;
|
|
13
16
|
#sessionKey;
|
|
17
|
+
#viaRemember = false;
|
|
18
|
+
#loggedOut = false;
|
|
19
|
+
#attemptedViaRemember = false;
|
|
14
20
|
constructor(config) {
|
|
15
21
|
this.#config = config;
|
|
16
22
|
this.#sessionKey = config.sessionKey ?? "auth_user_id";
|
|
17
23
|
}
|
|
24
|
+
/** The cookie the remember-me token travels in. */
|
|
25
|
+
get rememberMeCookieName() {
|
|
26
|
+
return this.#config.rememberMeCookieName ?? "remember_web";
|
|
27
|
+
}
|
|
28
|
+
/** Whether "keep me signed in" is wired at all. */
|
|
29
|
+
get usesRememberMeTokens() {
|
|
30
|
+
return this.#config.rememberMeTokens !== undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Whether the current user was revived from a remember-me cookie rather
|
|
34
|
+
* than signing in (AdonisJS `viaRemember`).
|
|
35
|
+
*
|
|
36
|
+
* This is the distinction that lets an app demand the password again before
|
|
37
|
+
* something sensitive — changing an email, spending money, deleting an
|
|
38
|
+
* account. Nothing reported it, so a session restored from a cookie looked
|
|
39
|
+
* exactly like one where the user had just typed their password.
|
|
40
|
+
*/
|
|
41
|
+
get viaRemember() {
|
|
42
|
+
return this.#viaRemember;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Whether a remember-me token was even tried on this request (AdonisJS
|
|
46
|
+
* `attemptedViaRemember`) — true whether or not it worked.
|
|
47
|
+
*/
|
|
48
|
+
get attemptedViaRemember() {
|
|
49
|
+
return this.#attemptedViaRemember;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Whether `logout()` ran on this guard (AdonisJS `isLoggedOut`).
|
|
53
|
+
*
|
|
54
|
+
* A handler that logs out and then keeps working — clearing a cart, writing
|
|
55
|
+
* an audit line — could not tell that the session was already gone.
|
|
56
|
+
*/
|
|
57
|
+
get isLoggedOut() {
|
|
58
|
+
return this.#loggedOut;
|
|
59
|
+
}
|
|
60
|
+
/** The session key the user id is stored under (AdonisJS `sessionKeyName`). */
|
|
61
|
+
get sessionKeyName() {
|
|
62
|
+
return this.#sessionKey;
|
|
63
|
+
}
|
|
64
|
+
/** The cookie key the remember-me token is stored under (AdonisJS `rememberMeKeyName`). */
|
|
65
|
+
get rememberMeKeyName() {
|
|
66
|
+
return this.rememberMeCookieName;
|
|
67
|
+
}
|
|
68
|
+
#rememberMeAge() {
|
|
69
|
+
return this.#config.rememberMeAge ?? DEFAULT_REMEMBER_AGE_SECONDS;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Mint a remember-me token for `user` and return the cookie value the caller
|
|
73
|
+
* must set. `null` when the feature is not wired.
|
|
74
|
+
*/
|
|
75
|
+
async issueRememberMeToken(user) {
|
|
76
|
+
const driver = this.#config.rememberMeTokens;
|
|
77
|
+
if (!driver)
|
|
78
|
+
return null;
|
|
79
|
+
const minted = mintRememberMeToken(user.id, this.#rememberMeAge());
|
|
80
|
+
await driver.create(minted.stored);
|
|
81
|
+
return minted.value;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Authenticate from a remember-me cookie, and RECYCLE it: the returned
|
|
85
|
+
* `cookieValue` must replace the one the browser holds.
|
|
86
|
+
*
|
|
87
|
+
* The user is re-read through `findUser`, never trusted from the token — a
|
|
88
|
+
* token outlives the row it points at, and a deleted or disabled account
|
|
89
|
+
* must not walk back in through a cookie.
|
|
90
|
+
*/
|
|
91
|
+
async authenticateViaRememberMeToken(cookieValue) {
|
|
92
|
+
const driver = this.#config.rememberMeTokens;
|
|
93
|
+
if (!driver)
|
|
94
|
+
return null;
|
|
95
|
+
this.#attemptedViaRemember = true;
|
|
96
|
+
const recycled = await verifyAndRecycleRememberMeToken(driver, cookieValue, this.#rememberMeAge());
|
|
97
|
+
if (!recycled)
|
|
98
|
+
return null;
|
|
99
|
+
const user = await this.#config.findUser(recycled.userId);
|
|
100
|
+
if (!user)
|
|
101
|
+
return null;
|
|
102
|
+
this.#viaRemember = true;
|
|
103
|
+
return { user, cookieValue: recycled.value };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Drop the token behind `cookieValue`. Called on logout so the persistent
|
|
107
|
+
* credential dies with the session — Adonis deletes the row too.
|
|
108
|
+
*/
|
|
109
|
+
async revokeRememberMeToken(cookieValue) {
|
|
110
|
+
const driver = this.#config.rememberMeTokens;
|
|
111
|
+
if (!driver)
|
|
112
|
+
return;
|
|
113
|
+
const decoded = decodeTokenValue(cookieValue);
|
|
114
|
+
if (!decoded)
|
|
115
|
+
return;
|
|
116
|
+
await driver.delete(decoded.identifier);
|
|
117
|
+
}
|
|
18
118
|
/** Authenticate via email/password — stores user ID in session. */
|
|
19
119
|
async authenticate(_credentials, _context) {
|
|
20
120
|
// Session strategy doesn't handle password verification — that's done
|
|
@@ -61,10 +161,33 @@ export class SessionStrategy {
|
|
|
61
161
|
async login(user, session) {
|
|
62
162
|
session.regenerate();
|
|
63
163
|
session.put(this.#sessionKey, user.id);
|
|
164
|
+
this.#loggedOut = false;
|
|
165
|
+
// A password was typed: this session is no longer "via remember", even
|
|
166
|
+
// if a cookie was tried earlier in the same request. Without the reset
|
|
167
|
+
// the flag would stay true and a re-auth prompt would never fire.
|
|
168
|
+
this.#viaRemember = false;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The session entry a test client needs to be `user` (AdonisJS
|
|
172
|
+
* `authenticateAsClient`).
|
|
173
|
+
*
|
|
174
|
+
* Returns the same key `login()` writes, so the request is authenticated by
|
|
175
|
+
* the guard's own logic rather than by the test knowing where the id lives.
|
|
176
|
+
*/
|
|
177
|
+
authenticateAsClient(user) {
|
|
178
|
+
return { session: { [this.#sessionKey]: user.id } };
|
|
64
179
|
}
|
|
65
|
-
/**
|
|
180
|
+
/**
|
|
181
|
+
* Logout — remove the user id from the session.
|
|
182
|
+
*
|
|
183
|
+
* The remember-me token is revoked separately through
|
|
184
|
+
* {@link revokeRememberMeToken}, because only the caller holds the cookie.
|
|
185
|
+
*/
|
|
66
186
|
async logout(session) {
|
|
67
187
|
session.forget(this.#sessionKey);
|
|
188
|
+
this.#viaRemember = false;
|
|
189
|
+
this.#attemptedViaRemember = false;
|
|
190
|
+
this.#loggedOut = true;
|
|
68
191
|
}
|
|
69
192
|
}
|
|
70
193
|
//# sourceMappingURL=SessionStrategy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionStrategy.js","sourceRoot":"","sources":["../../src/strategies/SessionStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"SessionStrategy.js","sourceRoot":"","sources":["../../src/strategies/SessionStrategy.ts"],"names":[],"mappings":"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"}
|
package/index.darwin-arm64.node
CHANGED
|
Binary file
|
package/index.darwin-x64.node
CHANGED
|
Binary file
|
|
Binary file
|
package/index.linux-x64-gnu.node
CHANGED
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c9up/warden",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "Warden — Authentication & authorization for the Ream framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@biomejs/biome": "^2.4.10",
|
|
65
|
+
"@c9up/quasar": "^0.1.0",
|
|
65
66
|
"@types/node": "^22.19.15",
|
|
66
67
|
"reflect-metadata": "^0.2.2",
|
|
67
68
|
"typescript": "^6.0.2",
|
|
@@ -85,6 +86,7 @@
|
|
|
85
86
|
"test:rust": "cargo test -p warden-engine",
|
|
86
87
|
"lint": "biome check src/",
|
|
87
88
|
"test:coverage": "vitest run --coverage",
|
|
88
|
-
"typecheck": "tsc --noEmit"
|
|
89
|
+
"typecheck": "tsc --noEmit",
|
|
90
|
+
"test:integration": "vitest run -c vitest.integration.config.ts"
|
|
89
91
|
}
|
|
90
92
|
}
|
package/src/AuthManager.ts
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
* @implements FR48, FR50, FR51
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { Authenticator } from "./Authenticator.js";
|
|
7
8
|
import { WardenError } from "./errors.js";
|
|
9
|
+
import type { WardenContext } from "./middleware.js";
|
|
10
|
+
import { sanitizePayload } from "./sanitize.js";
|
|
11
|
+
|
|
12
|
+
export { sanitizePayload };
|
|
13
|
+
|
|
8
14
|
import { MemoryRightsStore } from "./rights/MemoryRightsStore.js";
|
|
9
15
|
import { RightsResolver } from "./rights/RightsResolver.js";
|
|
10
16
|
import type { EffectivePermissions, Scope } from "./rights/types.js";
|
|
@@ -31,10 +37,53 @@ export interface AuthResult {
|
|
|
31
37
|
strategyCrash?: true;
|
|
32
38
|
}
|
|
33
39
|
|
|
40
|
+
/**
|
|
41
|
+
* What a test client must send to act as a user (AdonisJS `AuthClientResponse`).
|
|
42
|
+
*
|
|
43
|
+
* Each guard answers in its own currency — a header for a token guard, a
|
|
44
|
+
* session entry for a session guard — so the test harness applies whichever
|
|
45
|
+
* fields come back instead of knowing how each guard authenticates.
|
|
46
|
+
*/
|
|
47
|
+
export interface AuthClientResponse {
|
|
48
|
+
/** Headers to set on the request. */
|
|
49
|
+
headers?: Record<string, string>;
|
|
50
|
+
/** Session values to seed before the request. */
|
|
51
|
+
session?: Record<string, unknown>;
|
|
52
|
+
/** Cookies to send. */
|
|
53
|
+
cookies?: Record<string, string>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The event namespace for a guard, taken from its DRIVER.
|
|
58
|
+
*
|
|
59
|
+
* AdonisJS namespaces auth events per driver — `session_auth:*`,
|
|
60
|
+
* `access_tokens_auth:*`, `basic_auth:*` — so an app can audit session logins
|
|
61
|
+
* without also hearing about every bearer token. Warden emitted `session_auth:*`
|
|
62
|
+
* whatever authenticated, which told a session listener about JWT traffic and
|
|
63
|
+
* left no name to subscribe to for the others.
|
|
64
|
+
*
|
|
65
|
+
* Driven by the strategy's `name` rather than the guard's config key: a guard
|
|
66
|
+
* called `web` running a `SessionStrategy` is still the session driver, exactly
|
|
67
|
+
* as upstream. A driver already ending in `_auth` (`basic_auth`) keeps its name
|
|
68
|
+
* rather than growing a second suffix, which is how AdonisJS spells it.
|
|
69
|
+
*/
|
|
70
|
+
export function authEventPrefix(driverName: string | undefined): string {
|
|
71
|
+
if (!driverName) return "session_auth";
|
|
72
|
+
return driverName.endsWith("_auth") ? driverName : `${driverName}_auth`;
|
|
73
|
+
}
|
|
74
|
+
|
|
34
75
|
export interface AuthStrategy {
|
|
35
76
|
name: string;
|
|
36
77
|
authenticate(credentials: Record<string, unknown>): Promise<AuthResult>;
|
|
37
78
|
verify(token: string, context?: Record<string, unknown>): Promise<AuthResult>;
|
|
79
|
+
/**
|
|
80
|
+
* Build what a test client sends to be this user (AdonisJS
|
|
81
|
+
* `authenticateAsClient`). Optional: a guard with no test seam simply has
|
|
82
|
+
* none, and the harness says so instead of forging one.
|
|
83
|
+
*/
|
|
84
|
+
authenticateAsClient?(
|
|
85
|
+
...args: never[]
|
|
86
|
+
): Promise<AuthClientResponse> | AuthClientResponse;
|
|
38
87
|
}
|
|
39
88
|
|
|
40
89
|
/**
|
|
@@ -86,6 +135,14 @@ function isTokenIssuer(
|
|
|
86
135
|
/**
|
|
87
136
|
* Manages authentication strategies and provides guard/permission checks.
|
|
88
137
|
*/
|
|
138
|
+
/**
|
|
139
|
+
* The slice of an event emitter warden publishes through — `emit(name, data)`,
|
|
140
|
+
* which is what both `@c9up/ream`'s Emitter and Adonis' expose.
|
|
141
|
+
*/
|
|
142
|
+
export interface WardenEmitter {
|
|
143
|
+
emit(event: string, data: unknown): unknown;
|
|
144
|
+
}
|
|
145
|
+
|
|
89
146
|
export class AuthManager {
|
|
90
147
|
// AdonisJS names: `guards` (name → strategy) and `default` (the guard used
|
|
91
148
|
// when none is named). Renamed from the previous `strategies`/`defaultStrategy`.
|
|
@@ -93,6 +150,40 @@ export class AuthManager {
|
|
|
93
150
|
private default: string;
|
|
94
151
|
private readonly rights: RightsResolver;
|
|
95
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Where auth events go. Structural and optional: warden is a leaf and must
|
|
155
|
+
* not import an emitter — a host wires its own through `setEmitter`, and one
|
|
156
|
+
* that wires none simply gets no events.
|
|
157
|
+
*/
|
|
158
|
+
#emitter: WardenEmitter | undefined;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Publish auth events on `emitter`. AdonisJS emits six of them from its
|
|
162
|
+
* session guard, and an app that audits logins reads exactly those.
|
|
163
|
+
*/
|
|
164
|
+
setEmitter(emitter: WardenEmitter): this {
|
|
165
|
+
this.#emitter = emitter;
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Publish one auth event. Public so the per-request `Authenticator` can
|
|
171
|
+
* report what it observed — it holds the guard chain, the manager holds the
|
|
172
|
+
* emitter.
|
|
173
|
+
*/
|
|
174
|
+
emitAuthEvent(event: string, payload: Record<string, unknown>): void {
|
|
175
|
+
this.#emit(event, payload);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Fire-and-forget: an event listener must never break authentication. */
|
|
179
|
+
#emit(event: string, payload: Record<string, unknown>): void {
|
|
180
|
+
try {
|
|
181
|
+
void this.#emitter?.emit(event, payload);
|
|
182
|
+
} catch {
|
|
183
|
+
// A throwing listener is the listener's problem, not the login's.
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
96
187
|
constructor(config: AuthConfig) {
|
|
97
188
|
// Normalise the two accepted forms — AdonisJS `{ default, guards }` and
|
|
98
189
|
// the legacy `{ defaultStrategy, strategies }` — to the AdonisJS names.
|
|
@@ -257,6 +348,63 @@ export class AuthManager {
|
|
|
257
348
|
}
|
|
258
349
|
|
|
259
350
|
/** Get a registered strategy by name. */
|
|
351
|
+
/**
|
|
352
|
+
* An {@link Authenticator} bound to one request (AdonisJS
|
|
353
|
+
* `createAuthenticator`).
|
|
354
|
+
*
|
|
355
|
+
* What the HTTP middleware builds per request. Exposed so anything outside
|
|
356
|
+
* that path — a console command acting as a user, a background job — can
|
|
357
|
+
* build one without reaching for the class.
|
|
358
|
+
*/
|
|
359
|
+
createAuthenticator(ctx: WardenContext): Authenticator {
|
|
360
|
+
return new Authenticator(ctx, this);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* What a test client needs to authenticate as a user (AdonisJS
|
|
365
|
+
* `createAuthenticatorClient`).
|
|
366
|
+
*
|
|
367
|
+
* Delegates to the guard's own `authenticateAsClient`, so a guard with no
|
|
368
|
+
* test seam says so instead of having one forged for it.
|
|
369
|
+
*/
|
|
370
|
+
createAuthenticatorClient(): {
|
|
371
|
+
use(guard?: string): {
|
|
372
|
+
authenticateAsClient(
|
|
373
|
+
...args: never[]
|
|
374
|
+
): Promise<AuthClientResponse> | AuthClientResponse;
|
|
375
|
+
};
|
|
376
|
+
} {
|
|
377
|
+
return {
|
|
378
|
+
use: (guard?: string) => {
|
|
379
|
+
const strategy = this.getStrategy(guard);
|
|
380
|
+
const seam = strategy.authenticateAsClient?.bind(strategy);
|
|
381
|
+
if (!seam) {
|
|
382
|
+
throw new WardenError(
|
|
383
|
+
"STRATEGY_HAS_NO_CLIENT",
|
|
384
|
+
`Auth strategy '${guard ?? this.default}' has no authenticateAsClient() — it cannot forge a client request.`,
|
|
385
|
+
{
|
|
386
|
+
hint: "Implement authenticateAsClient() on the strategy, or authenticate through a real login in the test.",
|
|
387
|
+
},
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
return { authenticateAsClient: seam };
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* The event namespace a guard's events carry, resolved from its driver.
|
|
397
|
+
* Falls back to `session_auth` when the guard cannot be resolved, so a
|
|
398
|
+
* failure to look one up never becomes a second failure at emit time.
|
|
399
|
+
*/
|
|
400
|
+
eventPrefixFor(guardName?: string): string {
|
|
401
|
+
try {
|
|
402
|
+
return authEventPrefix(this.getStrategy(guardName).name);
|
|
403
|
+
} catch {
|
|
404
|
+
return authEventPrefix(undefined);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
260
408
|
getStrategy(name?: string): AuthStrategy {
|
|
261
409
|
const strategyName = name ?? this.default;
|
|
262
410
|
const strategy = this.guards.get(strategyName);
|
|
@@ -316,7 +464,17 @@ export class AuthManager {
|
|
|
316
464
|
},
|
|
317
465
|
);
|
|
318
466
|
}
|
|
467
|
+
const prefix = authEventPrefix(strategy.name);
|
|
468
|
+
this.#emit(`${prefix}:login_attempted`, {
|
|
469
|
+
guardName: strategyName ?? this.default,
|
|
470
|
+
user,
|
|
471
|
+
});
|
|
319
472
|
await strategy.login(user, session);
|
|
473
|
+
this.#emit(`${prefix}:login_succeeded`, {
|
|
474
|
+
guardName: strategyName ?? this.default,
|
|
475
|
+
user,
|
|
476
|
+
sessionId: undefined,
|
|
477
|
+
});
|
|
320
478
|
}
|
|
321
479
|
|
|
322
480
|
/**
|
|
@@ -333,6 +491,11 @@ export class AuthManager {
|
|
|
333
491
|
);
|
|
334
492
|
}
|
|
335
493
|
await strategy.logout(session);
|
|
494
|
+
this.#emit(`${authEventPrefix(strategy.name)}:logged_out`, {
|
|
495
|
+
guardName: strategyName ?? this.default,
|
|
496
|
+
user: null,
|
|
497
|
+
error: null,
|
|
498
|
+
});
|
|
336
499
|
}
|
|
337
500
|
}
|
|
338
501
|
|
|
@@ -366,10 +529,3 @@ function isLoginCapable(
|
|
|
366
529
|
* directly (not `AuthManager.verify`), so without this it would skip
|
|
367
530
|
* the guard JWT / api-key users get.
|
|
368
531
|
*/
|
|
369
|
-
export function sanitizePayload(user: UserPayload): void {
|
|
370
|
-
for (const key of ["__proto__", "constructor", "prototype"]) {
|
|
371
|
-
if (key in user) {
|
|
372
|
-
delete (user as Record<string, unknown>)[key];
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|