@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.
Files changed (124) hide show
  1. package/dist/AuthManager.d.ts +86 -1
  2. package/dist/AuthManager.d.ts.map +1 -1
  3. package/dist/AuthManager.js +113 -10
  4. package/dist/AuthManager.js.map +1 -1
  5. package/dist/Authenticator.d.ts +13 -1
  6. package/dist/Authenticator.d.ts.map +1 -1
  7. package/dist/Authenticator.js +37 -3
  8. package/dist/Authenticator.js.map +1 -1
  9. package/dist/RememberMeToken.d.ts +169 -0
  10. package/dist/RememberMeToken.d.ts.map +1 -0
  11. package/dist/RememberMeToken.js +269 -0
  12. package/dist/RememberMeToken.js.map +1 -0
  13. package/dist/ResilientBlacklistDriver.d.ts +15 -9
  14. package/dist/ResilientBlacklistDriver.d.ts.map +1 -1
  15. package/dist/ResilientBlacklistDriver.js +53 -15
  16. package/dist/ResilientBlacklistDriver.js.map +1 -1
  17. package/dist/Secret.d.ts +11 -0
  18. package/dist/Secret.d.ts.map +1 -0
  19. package/dist/Secret.js +36 -0
  20. package/dist/Secret.js.map +1 -0
  21. package/dist/WardenProvider.d.ts +1 -0
  22. package/dist/WardenProvider.d.ts.map +1 -1
  23. package/dist/WardenProvider.js +64 -0
  24. package/dist/WardenProvider.js.map +1 -1
  25. package/dist/bouncer/Bouncer.d.ts +46 -1
  26. package/dist/bouncer/Bouncer.d.ts.map +1 -1
  27. package/dist/bouncer/Bouncer.js +77 -4
  28. package/dist/bouncer/Bouncer.js.map +1 -1
  29. package/dist/bouncer/PolicyAuthorizer.d.ts +9 -1
  30. package/dist/bouncer/PolicyAuthorizer.d.ts.map +1 -1
  31. package/dist/bouncer/PolicyAuthorizer.js +17 -1
  32. package/dist/bouncer/PolicyAuthorizer.js.map +1 -1
  33. package/dist/bouncer/evaluate.d.ts +14 -2
  34. package/dist/bouncer/evaluate.d.ts.map +1 -1
  35. package/dist/bouncer/evaluate.js +16 -5
  36. package/dist/bouncer/evaluate.js.map +1 -1
  37. package/dist/bouncer/types.d.ts +4 -2
  38. package/dist/bouncer/types.d.ts.map +1 -1
  39. package/dist/config.d.ts +3 -0
  40. package/dist/config.d.ts.map +1 -1
  41. package/dist/config.js +5 -0
  42. package/dist/config.js.map +1 -1
  43. package/dist/firstcontact/drivers/GitHubDriver.d.ts.map +1 -1
  44. package/dist/firstcontact/drivers/GitHubDriver.js +2 -3
  45. package/dist/firstcontact/drivers/GitHubDriver.js.map +1 -1
  46. package/dist/firstcontact/drivers/GoogleDriver.d.ts.map +1 -1
  47. package/dist/firstcontact/drivers/GoogleDriver.js +2 -3
  48. package/dist/firstcontact/drivers/GoogleDriver.js.map +1 -1
  49. package/dist/firstcontact/types.d.ts +10 -0
  50. package/dist/firstcontact/types.d.ts.map +1 -1
  51. package/dist/firstcontact/types.js +18 -1
  52. package/dist/firstcontact/types.js.map +1 -1
  53. package/dist/index.d.ts +8 -4
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +6 -3
  56. package/dist/index.js.map +1 -1
  57. package/dist/mfa/MfaManager.d.ts.map +1 -1
  58. package/dist/mfa/MfaManager.js +14 -5
  59. package/dist/mfa/MfaManager.js.map +1 -1
  60. package/dist/mfa/TotpProvider.d.ts +34 -1
  61. package/dist/mfa/TotpProvider.d.ts.map +1 -1
  62. package/dist/mfa/TotpProvider.js +60 -4
  63. package/dist/mfa/TotpProvider.js.map +1 -1
  64. package/dist/mfa/webauthn-codec.d.ts +6 -1
  65. package/dist/mfa/webauthn-codec.d.ts.map +1 -1
  66. package/dist/mfa/webauthn-codec.js +50 -6
  67. package/dist/mfa/webauthn-codec.js.map +1 -1
  68. package/dist/middleware.d.ts.map +1 -1
  69. package/dist/middleware.js +46 -1
  70. package/dist/middleware.js.map +1 -1
  71. package/dist/quasar.d.ts.map +1 -1
  72. package/dist/quasar.js +6 -14
  73. package/dist/quasar.js.map +1 -1
  74. package/dist/sanitize.d.ts +10 -0
  75. package/dist/sanitize.d.ts.map +1 -0
  76. package/dist/sanitize.js +15 -0
  77. package/dist/sanitize.js.map +1 -0
  78. package/dist/strategies/ApiKeyStrategy.d.ts +10 -1
  79. package/dist/strategies/ApiKeyStrategy.d.ts.map +1 -1
  80. package/dist/strategies/ApiKeyStrategy.js +16 -0
  81. package/dist/strategies/ApiKeyStrategy.js.map +1 -1
  82. package/dist/strategies/BasicAuthStrategy.d.ts +59 -0
  83. package/dist/strategies/BasicAuthStrategy.d.ts.map +1 -0
  84. package/dist/strategies/BasicAuthStrategy.js +97 -0
  85. package/dist/strategies/BasicAuthStrategy.js.map +1 -0
  86. package/dist/strategies/JwtStrategy.d.ts +10 -1
  87. package/dist/strategies/JwtStrategy.d.ts.map +1 -1
  88. package/dist/strategies/JwtStrategy.js +11 -0
  89. package/dist/strategies/JwtStrategy.js.map +1 -1
  90. package/dist/strategies/SessionStrategy.d.ts +84 -2
  91. package/dist/strategies/SessionStrategy.d.ts.map +1 -1
  92. package/dist/strategies/SessionStrategy.js +124 -1
  93. package/dist/strategies/SessionStrategy.js.map +1 -1
  94. package/index.darwin-arm64.node +0 -0
  95. package/index.darwin-x64.node +0 -0
  96. package/index.linux-arm64-gnu.node +0 -0
  97. package/index.linux-x64-gnu.node +0 -0
  98. package/index.win32-x64-msvc.node +0 -0
  99. package/package.json +4 -2
  100. package/src/AuthManager.ts +163 -7
  101. package/src/Authenticator.ts +58 -10
  102. package/src/RememberMeToken.ts +357 -0
  103. package/src/ResilientBlacklistDriver.ts +58 -18
  104. package/src/Secret.ts +41 -0
  105. package/src/WardenProvider.ts +90 -0
  106. package/src/bouncer/Bouncer.ts +93 -4
  107. package/src/bouncer/PolicyAuthorizer.ts +20 -1
  108. package/src/bouncer/evaluate.ts +26 -6
  109. package/src/bouncer/types.ts +4 -2
  110. package/src/config.ts +9 -0
  111. package/src/firstcontact/drivers/GitHubDriver.ts +2 -3
  112. package/src/firstcontact/drivers/GoogleDriver.ts +2 -3
  113. package/src/firstcontact/types.ts +24 -0
  114. package/src/index.ts +30 -2
  115. package/src/mfa/MfaManager.ts +15 -8
  116. package/src/mfa/TotpProvider.ts +90 -6
  117. package/src/mfa/webauthn-codec.ts +48 -6
  118. package/src/middleware.ts +55 -2
  119. package/src/quasar.ts +6 -14
  120. package/src/sanitize.ts +17 -0
  121. package/src/strategies/ApiKeyStrategy.ts +23 -1
  122. package/src/strategies/BasicAuthStrategy.ts +127 -0
  123. package/src/strategies/JwtStrategy.ts +18 -1
  124. package/src/strategies/SessionStrategy.ts +167 -2
@@ -7,8 +7,19 @@
7
7
  * @implements MISS-7
8
8
  */
9
9
 
10
- import type { AuthResult, AuthStrategy, UserPayload } from "../AuthManager.js";
10
+ import type {
11
+ AuthClientResponse,
12
+ AuthResult,
13
+ AuthStrategy,
14
+ UserPayload,
15
+ } from "../AuthManager.js";
11
16
  import { WardenError } from "../errors.js";
17
+ import {
18
+ decodeTokenValue,
19
+ mintRememberMeToken,
20
+ type RememberMeTokenDriver,
21
+ verifyAndRecycleRememberMeToken,
22
+ } from "../RememberMeToken.js";
12
23
 
13
24
  export interface SessionStore {
14
25
  get(key: string): unknown;
@@ -33,18 +44,148 @@ export interface SessionStore {
33
44
  export interface SessionStrategyConfig {
34
45
  sessionKey?: string;
35
46
  findUser: (id: string | number) => Promise<UserPayload | null>;
47
+ /**
48
+ * Turn on "keep me signed in". Adonis calls the equivalent flag
49
+ * `useRememberMeTokens`; without a driver there is nowhere to persist the
50
+ * token, so passing one IS the opt-in.
51
+ */
52
+ rememberMeTokens?: RememberMeTokenDriver;
53
+ /**
54
+ * How long a remember-me token lives. Adonis defaults to two years, and a
55
+ * number here is SECONDS (`durationToSeconds` semantics).
56
+ */
57
+ rememberMeAge?: number;
58
+ /**
59
+ * Cookie name holding the token. Adonis derives `remember_<guard>`; the
60
+ * default matches the `web` guard an app is most likely migrating.
61
+ */
62
+ rememberMeCookieName?: string;
36
63
  }
37
64
 
65
+ /** Two years, Adonis' `rememberMeTokensAge` default. */
66
+ const DEFAULT_REMEMBER_AGE_SECONDS = 63_072_000;
67
+
38
68
  export class SessionStrategy implements AuthStrategy {
39
69
  name = "session";
40
70
  #config: SessionStrategyConfig;
41
71
  #sessionKey: string;
72
+ #viaRemember = false;
73
+ #loggedOut = false;
74
+ #attemptedViaRemember = false;
42
75
 
43
76
  constructor(config: SessionStrategyConfig) {
44
77
  this.#config = config;
45
78
  this.#sessionKey = config.sessionKey ?? "auth_user_id";
46
79
  }
47
80
 
81
+ /** The cookie the remember-me token travels in. */
82
+ get rememberMeCookieName(): string {
83
+ return this.#config.rememberMeCookieName ?? "remember_web";
84
+ }
85
+
86
+ /** Whether "keep me signed in" is wired at all. */
87
+ get usesRememberMeTokens(): boolean {
88
+ return this.#config.rememberMeTokens !== undefined;
89
+ }
90
+
91
+ /**
92
+ * Whether the current user was revived from a remember-me cookie rather
93
+ * than signing in (AdonisJS `viaRemember`).
94
+ *
95
+ * This is the distinction that lets an app demand the password again before
96
+ * something sensitive — changing an email, spending money, deleting an
97
+ * account. Nothing reported it, so a session restored from a cookie looked
98
+ * exactly like one where the user had just typed their password.
99
+ */
100
+ get viaRemember(): boolean {
101
+ return this.#viaRemember;
102
+ }
103
+
104
+ /**
105
+ * Whether a remember-me token was even tried on this request (AdonisJS
106
+ * `attemptedViaRemember`) — true whether or not it worked.
107
+ */
108
+ get attemptedViaRemember(): boolean {
109
+ return this.#attemptedViaRemember;
110
+ }
111
+
112
+ /**
113
+ * Whether `logout()` ran on this guard (AdonisJS `isLoggedOut`).
114
+ *
115
+ * A handler that logs out and then keeps working — clearing a cart, writing
116
+ * an audit line — could not tell that the session was already gone.
117
+ */
118
+ get isLoggedOut(): boolean {
119
+ return this.#loggedOut;
120
+ }
121
+
122
+ /** The session key the user id is stored under (AdonisJS `sessionKeyName`). */
123
+ get sessionKeyName(): string {
124
+ return this.#sessionKey;
125
+ }
126
+
127
+ /** The cookie key the remember-me token is stored under (AdonisJS `rememberMeKeyName`). */
128
+ get rememberMeKeyName(): string {
129
+ return this.rememberMeCookieName;
130
+ }
131
+
132
+ #rememberMeAge(): number {
133
+ return this.#config.rememberMeAge ?? DEFAULT_REMEMBER_AGE_SECONDS;
134
+ }
135
+
136
+ /**
137
+ * Mint a remember-me token for `user` and return the cookie value the caller
138
+ * must set. `null` when the feature is not wired.
139
+ */
140
+ async issueRememberMeToken(user: UserPayload): Promise<string | null> {
141
+ const driver = this.#config.rememberMeTokens;
142
+ if (!driver) return null;
143
+ const minted = mintRememberMeToken(user.id, this.#rememberMeAge());
144
+ await driver.create(minted.stored);
145
+ return minted.value;
146
+ }
147
+
148
+ /**
149
+ * Authenticate from a remember-me cookie, and RECYCLE it: the returned
150
+ * `cookieValue` must replace the one the browser holds.
151
+ *
152
+ * The user is re-read through `findUser`, never trusted from the token — a
153
+ * token outlives the row it points at, and a deleted or disabled account
154
+ * must not walk back in through a cookie.
155
+ */
156
+ async authenticateViaRememberMeToken(
157
+ cookieValue: unknown,
158
+ ): Promise<{ user: UserPayload; cookieValue: string } | null> {
159
+ const driver = this.#config.rememberMeTokens;
160
+ if (!driver) return null;
161
+ this.#attemptedViaRemember = true;
162
+
163
+ const recycled = await verifyAndRecycleRememberMeToken(
164
+ driver,
165
+ cookieValue,
166
+ this.#rememberMeAge(),
167
+ );
168
+ if (!recycled) return null;
169
+
170
+ const user = await this.#config.findUser(recycled.userId);
171
+ if (!user) return null;
172
+
173
+ this.#viaRemember = true;
174
+ return { user, cookieValue: recycled.value };
175
+ }
176
+
177
+ /**
178
+ * Drop the token behind `cookieValue`. Called on logout so the persistent
179
+ * credential dies with the session — Adonis deletes the row too.
180
+ */
181
+ async revokeRememberMeToken(cookieValue: unknown): Promise<void> {
182
+ const driver = this.#config.rememberMeTokens;
183
+ if (!driver) return;
184
+ const decoded = decodeTokenValue(cookieValue);
185
+ if (!decoded) return;
186
+ await driver.delete(decoded.identifier);
187
+ }
188
+
48
189
  /** Authenticate via email/password — stores user ID in session. */
49
190
  async authenticate(
50
191
  _credentials: { email: string; password: string },
@@ -102,10 +243,34 @@ export class SessionStrategy implements AuthStrategy {
102
243
  async login(user: UserPayload, session: SessionStore): Promise<void> {
103
244
  session.regenerate();
104
245
  session.put(this.#sessionKey, user.id);
246
+ this.#loggedOut = false;
247
+ // A password was typed: this session is no longer "via remember", even
248
+ // if a cookie was tried earlier in the same request. Without the reset
249
+ // the flag would stay true and a re-auth prompt would never fire.
250
+ this.#viaRemember = false;
251
+ }
252
+
253
+ /**
254
+ * The session entry a test client needs to be `user` (AdonisJS
255
+ * `authenticateAsClient`).
256
+ *
257
+ * Returns the same key `login()` writes, so the request is authenticated by
258
+ * the guard's own logic rather than by the test knowing where the id lives.
259
+ */
260
+ authenticateAsClient(user: UserPayload): AuthClientResponse {
261
+ return { session: { [this.#sessionKey]: user.id } };
105
262
  }
106
263
 
107
- /** Logout — remove user ID from session. */
264
+ /**
265
+ * Logout — remove the user id from the session.
266
+ *
267
+ * The remember-me token is revoked separately through
268
+ * {@link revokeRememberMeToken}, because only the caller holds the cookie.
269
+ */
108
270
  async logout(session: SessionStore): Promise<void> {
109
271
  session.forget(this.#sessionKey);
272
+ this.#viaRemember = false;
273
+ this.#attemptedViaRemember = false;
274
+ this.#loggedOut = true;
110
275
  }
111
276
  }