@spfn/auth 0.3.0-beta.2 → 0.3.0-beta.20
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/README.md +1382 -23
- package/dist/client-proof.d.ts +45 -15
- package/dist/client-proof.js +198 -4
- package/dist/client-proof.js.map +1 -1
- package/dist/client.d.ts +92 -1
- package/dist/client.js +58 -0
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +302 -0
- package/dist/config.js +134 -4
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +370 -3
- package/dist/errors.js +245 -2
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +185 -2
- package/dist/index.js +256 -2
- package/dist/index.js.map +1 -1
- package/dist/machine-principals-BD4tnASp.d.ts +2739 -0
- package/dist/nextjs/api.js +350 -12
- package/dist/nextjs/api.js.map +1 -1
- package/dist/nextjs/client.d.ts +28 -1
- package/dist/nextjs/client.js +24 -3
- package/dist/nextjs/client.js.map +1 -1
- package/dist/nextjs/server.d.ts +173 -3
- package/dist/nextjs/server.js +372 -10
- package/dist/nextjs/server.js.map +1 -1
- package/dist/server.d.ts +3761 -414
- package/dist/server.js +5865 -1043
- package/dist/server.js.map +1 -1
- package/dist/{session-DTHahDQ9.d.ts → session-Dfwu5g2W.d.ts} +28 -1
- package/migrations/20260810112144_colorful_tomorrow_man/migration.sql +18 -0
- package/migrations/20260810112144_colorful_tomorrow_man/snapshot.json +3576 -0
- package/migrations/20260901091716_fine_arclight/migration.sql +21 -0
- package/migrations/20260901091716_fine_arclight/snapshot.json +3849 -0
- package/migrations/20260906155957_natural_moonstone/migration.sql +33 -0
- package/migrations/20260906155957_natural_moonstone/snapshot.json +4275 -0
- package/migrations/20260907020904_giant_eternals/migration.sql +21 -0
- package/migrations/20260907020904_giant_eternals/snapshot.json +4561 -0
- package/migrations/20260907044807_eminent_angel/migration.sql +2 -0
- package/migrations/20260907044807_eminent_angel/snapshot.json +4561 -0
- package/migrations/20260918083158_foamy_roughhouse/migration.sql +55 -0
- package/migrations/20260918083158_foamy_roughhouse/snapshot.json +5271 -0
- package/package.json +9 -6
- package/dist/authenticate-55LeXHqZ.d.ts +0 -1447
package/dist/server.d.ts
CHANGED
|
@@ -1,24 +1,316 @@
|
|
|
1
|
-
import { A as AuthInitOptions,
|
|
2
|
-
export {
|
|
1
|
+
import { A as AuthInitOptions, u as OAuthProvider, s as VerificationPurpose, N as NewPasskey, v as Passkey, q as PermissionCategory, w as AuthContext } from './machine-principals-BD4tnASp.js';
|
|
2
|
+
export { x as ApproveDeviceAuthParams, y as AuthProfileOutcome, z as AuthProfileVerifier, B as ChangePasswordParams, E as CompletePasswordResetParams, G as CompleteSignupParams, H as ConfirmPasswordResetParams, d as ConfirmPasswordResetResult, J as ConfirmSignupLinkParams, C as ConfirmSignupLinkResult, M as DenyDeviceAuthParams, Q as DeviceAuthApprovedResult, T as DeviceAuthInfoParams, D as DeviceAuthInfoResult, W as DeviceAuthPendingResult, X as DeviceAuthPollResponseSchema, Y as DeviceNameSchema, Z as EmailSchema, _ as FingerprintSchema, $ as FinishPasskeyEnrollmentParams, F as FinishPasskeyEnrollmentResult, a0 as FinishPasskeyLoginParams, I as IssueOneTimeTokenResult, a1 as KEY_FINGERPRINT_PREFIX_LENGTH, a2 as KeyIdSchema, K as KeySummary, a3 as LoginParams, L as LoginResult, a4 as LogoutParams, a5 as MachinePrincipal, a6 as MachineVerifierRegistration, a7 as NativeVerifyOptions, a8 as NormalizedIdentity, l as OAuth2AuthorizationCodeIssued, a9 as OAuth2AuthorizeParams, k as OAuth2ConsentView, n as OAuth2GrantSummary, aa as OAuth2ScopeDescription, ab as OAuthCallbackParams, ac as OAuthCallbackResult, ad as OAuthCodeExchangeOptions, ae as OAuthNativeParams, i as OAuthNativeResult, af as OAuthStartParams, O as OAuthStartResult, ag as OAuthTokens, ah as PASSKEY_DEVICE_TYPES, ai as PASSKEY_LABEL_MAX_LENGTH, aj as PasskeyDeviceType, f as PasskeySummary, ak as PasswordSchema, al as PhoneSchema, am as PlatformSchema, an as PollDeviceAuthParams, ao as PollDeviceAuthResult, ap as PublicKeySchema, aq as RecentAuthenticationParams, ar as RegisterParams, as as RegisterPublicKeyParams, a as RegisterResult, at as RenamePasskeyParams, au as RequestPasswordResetParams, c as RequestPasswordResetResult, av as RequestSignupLinkParams, b as RequestSignupLinkResult, aw as RevokeAllKeysParams, h as RevokeAllKeysResult, ax as RevokeKeyParams, ay as RevokePasskeyParams, az as RotateKeyParams, g as RotateKeyResult, aA as SendVerificationCodeParams, S as SendVerificationCodeResult, aB as StartDeviceAuthParams, e as StartDeviceAuthResult, aC as StartPasskeyEnrollmentParams, aD as TargetTypeSchema, aE as UnlinkNotification, aF as UnlinkNotifyRejection, aG as UnlinkNotifyRequest, aH as UnlinkNotifyResult, aI as UserCodeSchema, V as VERIFICATION_PURPOSES, r as VERIFICATION_TARGET_TYPES, aJ as VerificationPurposeSchema, t as VerificationTargetType, aK as VerifyCodeParams, aL as VerifyCodeResult, aM as approveDeviceAuthService, aN as approveOAuth2AuthorizeService, aO as assertNotLastRecoveryCredential, aP as assertRecentAuthentication, m as authRouter, aQ as authenticate, aR as buildOAuthErrorUrl, aS as changePasswordService, aT as completePasswordResetService, aU as completeSignupService, aV as confirmPasswordResetService, aW as confirmSignupLinkService, aX as denyDeviceAuthService, aY as denyOAuth2AuthorizeService, aZ as describeOAuth2AuthorizeRequestService, a_ as finishPasskeyEnrollmentService, a$ as finishPasskeyLoginService, b0 as getDeviceAuthInfoService, b1 as getEnabledOAuthProviders, b2 as getGoogleAccessToken, b3 as getMachinePrincipal, b4 as getOAuthProvider, b5 as getRegisteredProviders, b6 as isOAuthProviderEnabled, b7 as issueOneTimeTokenService, b8 as listKeysService, b9 as listOAuth2GrantsService, ba as listPasskeysService, bb as loginService, bc as logoutService, bd as machineAuth, be as oauthCallbackService, bf as oauthNativeService, bg as oauthStartService, bh as oauthUnlinkNotifyService, bi as optionalAuth, bj as passkeys, bk as pollDeviceAuthService, bl as registerAuthProfile, bm as registerMachineVerifier, bn as registerOAuthProvider, bo as registerPublicKeyService, bp as registerService, bq as renamePasskeyService, br as requestPasswordResetService, bs as requestSignupLinkService, bt as requireEnabledProvider, bu as requireMachineScope, bv as resolveAuthenticatedUser, bw as revokeAllKeysService, bx as revokeAllOAuth2GrantsForUser, by as revokeKeyService, bz as revokeOAuth2GrantService, bA as revokePasskeyService, bB as rotateKeyService, bC as runAuthProfile, bD as selectAuthProfile, bE as sendVerificationCodeService, bF as startDeviceAuthService, bG as startPasskeyEnrollmentService, bH as startPasskeyLoginService, bI as verifyCodeService, bJ as verifyOneTimeTokenService } from './machine-principals-BD4tnASp.js';
|
|
3
3
|
import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
4
|
-
import {
|
|
4
|
+
import { j as SocialProvider, K as KeyAlgorithmType, d as InvitationStatus, c as AccountDeletionRequestedBy, i as PurgeStrategy } from './types-DYyhze28.js';
|
|
5
5
|
export { A as ACCOUNT_DELETION_REQUESTED_BY, a as ACCOUNT_DELETION_REQUEST_STATUSES, b as AccountDeletionRequestStatus, I as INVITATION_STATUSES, e as KEY_ALGORITHM, f as KEY_DEVICE_NAME_MAX_LENGTH, g as KEY_PLATFORM, h as KeyPlatformType, P as PURGE_STRATEGIES, S as SOCIAL_PROVIDERS, U as USER_STATUSES, k as UserStatus } from './types-DYyhze28.js';
|
|
6
6
|
import { UserProfile as UserProfile$1, ProfileInfo } from '@spfn/auth';
|
|
7
7
|
import { BaseRepository } from '@spfn/core/db';
|
|
8
8
|
import { c as ClientIdentity } from './wire-version-CtzMKvBB.js';
|
|
9
9
|
import { Context } from 'hono';
|
|
10
10
|
import * as _spfn_core_route from '@spfn/core/route';
|
|
11
|
+
import { NamedMiddleware } from '@spfn/core/route';
|
|
11
12
|
export { KeyPair, generateClientToken, generateKeyPair, generateKeyPairES256, generateKeyPairRS256, getKeySize, shouldRotateKey } from './crypto.js';
|
|
12
|
-
export { S as SessionData, g as getSessionInfo, s as sealSession, a as shouldRefreshSession, u as unsealSession } from './session-
|
|
13
|
+
export { S as SessionData, g as getSessionInfo, i as isSafeReturnPath, s as sealSession, a as shouldRefreshSession, u as unsealSession } from './session-Dfwu5g2W.js';
|
|
13
14
|
import { JWTPayload } from 'jose';
|
|
14
15
|
import { SSETokenManager, SSETokenStore } from '@spfn/core/event/sse';
|
|
15
16
|
import * as _spfn_core_logger from '@spfn/core/logger';
|
|
16
17
|
import * as _spfn_core_job from '@spfn/core/job';
|
|
17
18
|
import * as _spfn_core_event from '@spfn/core/event';
|
|
18
19
|
import * as _sinclair_typebox from '@sinclair/typebox';
|
|
20
|
+
import '@simplewebauthn/server';
|
|
19
21
|
import '@spfn/auth/server';
|
|
20
22
|
import 'jsonwebtoken';
|
|
21
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @spfn/auth - Global Configuration
|
|
26
|
+
*
|
|
27
|
+
* Manages global auth configuration including session TTL
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Cookie names used by SPFN Auth
|
|
32
|
+
*
|
|
33
|
+
* Names include a port-based suffix so that multiple dev instances
|
|
34
|
+
* on different ports don't overwrite each other's cookies.
|
|
35
|
+
*/
|
|
36
|
+
declare const COOKIE_NAMES: {
|
|
37
|
+
/** Encrypted session data (userId, privateKey, keyId, algorithm) */
|
|
38
|
+
readonly SESSION: string;
|
|
39
|
+
/** Current key ID (for key rotation) */
|
|
40
|
+
readonly SESSION_KEY_ID: string;
|
|
41
|
+
/** Pending OAuth session (privateKey, keyId, algorithm) - temporary during OAuth flow */
|
|
42
|
+
readonly OAUTH_PENDING: string;
|
|
43
|
+
/** OAuth CSRF nonce — double-submit against the (encrypted) state.nonce at callback */
|
|
44
|
+
readonly OAUTH_CSRF: string;
|
|
45
|
+
/** Password-setup session for verified-email signup — temporary, single-purpose */
|
|
46
|
+
readonly SIGNUP_SETUP: string;
|
|
47
|
+
/** Password-setup session for a password reset — temporary, single-purpose */
|
|
48
|
+
readonly PASSWORD_RESET_SETUP: string;
|
|
49
|
+
/** CSRF token — the only cookie here the browser can read */
|
|
50
|
+
readonly CSRF: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* OAuth CSRF 쿠키를 PORT 접미사와 무관하게 전부 수집한다.
|
|
54
|
+
*
|
|
55
|
+
* 쿠키를 심는 쪽은 Next.js 프로세스, 읽는 쪽은 API 프로세스라 분리 배포에서는
|
|
56
|
+
* 두 프로세스의 PORT가 달라 COOKIE_NAMES.OAUTH_CSRF 정확 일치 조회가 빗나간다.
|
|
57
|
+
* nonce 자체가 랜덤값이고 암호화된 state의 nonce와 대조되므로, 접미사가 다른
|
|
58
|
+
* spfn_oauth_csrf* 후보를 모두 대조 대상으로 넘겨도 안전하다.
|
|
59
|
+
*/
|
|
60
|
+
declare function matchOAuthCsrfCookies(cookies: Record<string, string>): {
|
|
61
|
+
name: string;
|
|
62
|
+
value: string;
|
|
63
|
+
}[];
|
|
64
|
+
/**
|
|
65
|
+
* Parse duration string to seconds
|
|
66
|
+
*
|
|
67
|
+
* Supports: '30d', '12h', '45m', '3600s', or plain number
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* parseDuration('30d') // 2592000 (30 days in seconds)
|
|
71
|
+
* parseDuration('12h') // 43200
|
|
72
|
+
* parseDuration('45m') // 2700
|
|
73
|
+
* parseDuration('3600') // 3600
|
|
74
|
+
*/
|
|
75
|
+
declare function parseDuration(duration: string | number): number;
|
|
76
|
+
/**
|
|
77
|
+
* Registration channel passed to the beforeRegister hook
|
|
78
|
+
*
|
|
79
|
+
* - credentials: email/phone + password registration
|
|
80
|
+
* - oauth: new-user signup through a social provider (web or native flow)
|
|
81
|
+
* - invitation: invitation acceptance
|
|
82
|
+
*/
|
|
83
|
+
type RegisterChannel = 'credentials' | 'oauth' | 'invitation';
|
|
84
|
+
/**
|
|
85
|
+
* Context passed to the beforeRegister hook
|
|
86
|
+
*
|
|
87
|
+
* Credentials (password, keys) are intentionally excluded — the hook is a
|
|
88
|
+
* policy gate, not a credential handler.
|
|
89
|
+
*/
|
|
90
|
+
interface BeforeRegisterContext {
|
|
91
|
+
channel: RegisterChannel;
|
|
92
|
+
/** Social provider — only set when channel is 'oauth' */
|
|
93
|
+
provider?: SocialProvider;
|
|
94
|
+
/**
|
|
95
|
+
* Canonical form of the address — trimmed and lower-cased, the same form
|
|
96
|
+
* the account is stored under. A policy keyed on the address (a denylist, a
|
|
97
|
+
* domain allowlist) therefore matches whatever capitalization the person
|
|
98
|
+
* typed, instead of being walked past by `Blocked@Example.com`.
|
|
99
|
+
*/
|
|
100
|
+
email?: string;
|
|
101
|
+
/**
|
|
102
|
+
* Whether the email is verified — only set when channel is 'oauth'.
|
|
103
|
+
* OAuth providers may report an unverified (spoofable) email; the created
|
|
104
|
+
* account stores it as null in that case, so email-based policies must
|
|
105
|
+
* check this flag. credentials/invitation emails are already verified.
|
|
106
|
+
*/
|
|
107
|
+
emailVerified?: boolean;
|
|
108
|
+
phone?: string;
|
|
109
|
+
/** App-supplied registration metadata (register params / OAuth start params / invitation) */
|
|
110
|
+
metadata?: Record<string, unknown>;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* How the Next.js proxy treats a cookie-authenticated mutation that arrives
|
|
114
|
+
* without a valid CSRF header.
|
|
115
|
+
*
|
|
116
|
+
* - `off`: no check
|
|
117
|
+
* - `warn`: allow it through, log one line per request that would be refused
|
|
118
|
+
* - `enforce`: refuse it with 403
|
|
119
|
+
*/
|
|
120
|
+
type CsrfMode = 'off' | 'warn' | 'enforce';
|
|
121
|
+
/**
|
|
122
|
+
* CSRF configuration for the Next.js proxy
|
|
123
|
+
*/
|
|
124
|
+
interface AuthCsrfConfig {
|
|
125
|
+
/**
|
|
126
|
+
* @default 'warn' — an existing app gets signal before it gets breakage.
|
|
127
|
+
* `SPFN_AUTH_CSRF` sets it when this is not; new apps scaffolded by
|
|
128
|
+
* `spfn init` are given `enforce`.
|
|
129
|
+
*/
|
|
130
|
+
mode?: CsrfMode;
|
|
131
|
+
/**
|
|
132
|
+
* Backend paths that skip the check, matched exactly.
|
|
133
|
+
*
|
|
134
|
+
* These are route paths as the backend sees them (`/webhooks/stripe`), not
|
|
135
|
+
* `/api/rpc/...` URLs, with route params already substituted. Intended for
|
|
136
|
+
* endpoints a browser session never calls — webhook receivers and the like.
|
|
137
|
+
* A path listed here is unprotected for cookie callers too, so list only
|
|
138
|
+
* endpoints that carry their own authentication.
|
|
139
|
+
*/
|
|
140
|
+
exemptPaths?: string[];
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Auth configuration
|
|
144
|
+
*/
|
|
145
|
+
interface AuthConfig {
|
|
146
|
+
/**
|
|
147
|
+
* Default session TTL in seconds or duration string
|
|
148
|
+
*
|
|
149
|
+
* Supports:
|
|
150
|
+
* - Number: seconds (e.g., 2592000)
|
|
151
|
+
* - String: '30d', '12h', '45m', '3600s'
|
|
152
|
+
*
|
|
153
|
+
* @default 7d (7 days)
|
|
154
|
+
*/
|
|
155
|
+
sessionTtl?: string | number;
|
|
156
|
+
/**
|
|
157
|
+
* App-injected validator that runs before a new user row is created,
|
|
158
|
+
* on every registration channel (credentials, oauth, invitation).
|
|
159
|
+
*
|
|
160
|
+
* Throw to reject the registration — RegistrationRejectedError (403) is
|
|
161
|
+
* the recommended error; any HttpError subclass keeps its own status.
|
|
162
|
+
* Runs after built-in checks (verification token, duplicate account),
|
|
163
|
+
* so existing error precedence is unchanged. Not called for admin
|
|
164
|
+
* seeding (initializeAuth) or when linking a social account to an
|
|
165
|
+
* existing user.
|
|
166
|
+
*
|
|
167
|
+
* Runs inside the registration DB transaction on every channel — keep it
|
|
168
|
+
* fast. A slow call (e.g. an external policy API) holds a pooled DB
|
|
169
|
+
* connection open for its full duration on every signup.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* configureAuth({
|
|
174
|
+
* beforeRegister: async ({ channel, metadata }) =>
|
|
175
|
+
* {
|
|
176
|
+
* if (channel === 'credentials' && !isOldEnough(metadata?.birthDate))
|
|
177
|
+
* {
|
|
178
|
+
* throw new RegistrationRejectedError({ message: 'Age requirement not met' });
|
|
179
|
+
* }
|
|
180
|
+
* },
|
|
181
|
+
* });
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
beforeRegister?: (context: BeforeRegisterContext) => void | Promise<void>;
|
|
185
|
+
/**
|
|
186
|
+
* CSRF protection for cookie-session mutations, enforced in the Next.js proxy.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```typescript
|
|
190
|
+
* configureAuth({
|
|
191
|
+
* csrf: { mode: 'enforce', exemptPaths: ['/webhooks/stripe'] },
|
|
192
|
+
* });
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
csrf?: AuthCsrfConfig;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Configure global auth settings
|
|
199
|
+
*
|
|
200
|
+
* @param config - Auth configuration
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```typescript
|
|
204
|
+
* configureAuth({
|
|
205
|
+
* sessionTtl: '30d', // 30 days
|
|
206
|
+
* });
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
declare function configureAuth(config: AuthConfig): void;
|
|
210
|
+
/**
|
|
211
|
+
* Get current auth configuration
|
|
212
|
+
*/
|
|
213
|
+
declare function getAuthConfig(): AuthConfig;
|
|
214
|
+
/**
|
|
215
|
+
* Run the app-injected beforeRegister hook if configured — throws to reject.
|
|
216
|
+
*
|
|
217
|
+
* Single entry point for every registration channel so a new channel cannot
|
|
218
|
+
* forget the configured-check. Callers invoke this right before creating the
|
|
219
|
+
* user row.
|
|
220
|
+
*
|
|
221
|
+
* The address is folded here rather than at each call site, for the same reason
|
|
222
|
+
* the check itself lives here: three channels supply it, and a policy that sees
|
|
223
|
+
* a different spelling depending on which one the person came through is a
|
|
224
|
+
* policy that can be walked past.
|
|
225
|
+
*/
|
|
226
|
+
declare function runBeforeRegister(context: BeforeRegisterContext): Promise<void>;
|
|
227
|
+
/**
|
|
228
|
+
* Get session TTL in seconds
|
|
229
|
+
*
|
|
230
|
+
* Priority:
|
|
231
|
+
* 1. Runtime override (remember parameter)
|
|
232
|
+
* 2. Global config (configureAuth)
|
|
233
|
+
* 3. Environment variable (SPFN_AUTH_SESSION_TTL) - via config module
|
|
234
|
+
* 4. Default (7 days)
|
|
235
|
+
*/
|
|
236
|
+
declare function getSessionTtl(override?: string | number): number;
|
|
237
|
+
/**
|
|
238
|
+
* Get the CSRF mode
|
|
239
|
+
*
|
|
240
|
+
* Priority:
|
|
241
|
+
* 1. Global config (configureAuth)
|
|
242
|
+
* 2. Environment variable (SPFN_AUTH_CSRF)
|
|
243
|
+
* 3. Default ('warn')
|
|
244
|
+
*
|
|
245
|
+
* An unrecognized value is a typo in the one setting that turns the check on;
|
|
246
|
+
* it resolves to `enforce` and says so, rather than quietly leaving mutations
|
|
247
|
+
* unprotected. It says so once per process: this runs on every mutation, so a
|
|
248
|
+
* per-call error would be pure repetition burying the rest of the log.
|
|
249
|
+
*/
|
|
250
|
+
declare function getCsrfMode(): CsrfMode;
|
|
251
|
+
/**
|
|
252
|
+
* Get the paths exempted from the CSRF check (exact match, backend route paths)
|
|
253
|
+
*/
|
|
254
|
+
declare function getCsrfExemptPaths(): string[];
|
|
255
|
+
/**
|
|
256
|
+
* The relying party this deployment presents to authenticators, resolved.
|
|
257
|
+
*
|
|
258
|
+
* `rpId` is the domain a credential is bound to and can never change without
|
|
259
|
+
* orphaning every passkey already enrolled under it. `origins` is the closed set
|
|
260
|
+
* of pages allowed to run a ceremony for that rpId.
|
|
261
|
+
*/
|
|
262
|
+
interface PasskeyConfig {
|
|
263
|
+
/** Domain credentials are bound to — a registrable domain, no protocol, no port. */
|
|
264
|
+
rpId: string;
|
|
265
|
+
/** Name shown by the authenticator's own prompt. */
|
|
266
|
+
rpName: string;
|
|
267
|
+
/** Full origins allowed to run a ceremony, e.g. `https://app.example.com`. */
|
|
268
|
+
origins: string[];
|
|
269
|
+
userVerification: PasskeyUserVerification;
|
|
270
|
+
challengeTtlMs: number;
|
|
271
|
+
recentAuthMs: number;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* How hard the authenticator must work to prove the person is present.
|
|
275
|
+
*
|
|
276
|
+
* `discouraged` is not offered: a passkey here is the whole credential, so an
|
|
277
|
+
* assertion that skipped user verification would sign someone in on possession
|
|
278
|
+
* of an unlocked device alone.
|
|
279
|
+
*/
|
|
280
|
+
type PasskeyUserVerification = 'preferred' | 'required';
|
|
281
|
+
type PasskeyEnvSource = Record<string, string | undefined>;
|
|
282
|
+
/**
|
|
283
|
+
* Resolve the passkey configuration, refusing anything a ceremony would fail on.
|
|
284
|
+
*
|
|
285
|
+
* Zero-config for a one-origin app: rpId is the app URL's host and the single
|
|
286
|
+
* origin is the app URL's origin. An app on several hosts sets
|
|
287
|
+
* `SPFN_AUTH_PASSKEY_RP_ID` to the registrable domain they share and lists them
|
|
288
|
+
* in `SPFN_AUTH_PASSKEY_ORIGINS`.
|
|
289
|
+
*
|
|
290
|
+
* @param env - Environment to read; defaults to `process.env`.
|
|
291
|
+
* @throws PasskeyConfigError when the configuration cannot be honoured.
|
|
292
|
+
*/
|
|
293
|
+
declare function getPasskeyConfig(env?: PasskeyEnvSource): PasskeyConfig;
|
|
294
|
+
/**
|
|
295
|
+
* Refuse boot on a passkey configuration no ceremony could satisfy.
|
|
296
|
+
*
|
|
297
|
+
* Resolution is the check: everything `getPasskeyConfig` refuses would otherwise
|
|
298
|
+
* surface as the browser rejecting every ceremony, long after the deploy that
|
|
299
|
+
* introduced the drift.
|
|
300
|
+
*
|
|
301
|
+
* The refusal is reserved for a configuration an operator actually wrote, which
|
|
302
|
+
* is the posture `assertOAuthRedirectUris` already takes for the same reason. An
|
|
303
|
+
* app that set no passkey variable at all can still resolve to something
|
|
304
|
+
* unusable — `SPFN_APP_URL=http://192.168.1.5:3000` for mobile development, say,
|
|
305
|
+
* which is neither https nor localhost — and refusing to start over a feature
|
|
306
|
+
* nobody asked for would take that app down to fix something it does not use.
|
|
307
|
+
* It is reported instead, once, and the first ceremony (if there ever is one)
|
|
308
|
+
* fails with the same message.
|
|
309
|
+
*
|
|
310
|
+
* @throws PasskeyConfigError when a passkey variable is set and cannot be honoured
|
|
311
|
+
*/
|
|
312
|
+
declare function assertPasskeyConfig(env?: PasskeyEnvSource): void;
|
|
313
|
+
|
|
22
314
|
/**
|
|
23
315
|
* @spfn/auth - Users Entity
|
|
24
316
|
*
|
|
@@ -431,6 +723,42 @@ declare function updateUsernameService(userId: string | number | bigint, usernam
|
|
|
431
723
|
*/
|
|
432
724
|
declare function initializeAuth(options?: AuthInitOptions): Promise<void>;
|
|
433
725
|
|
|
726
|
+
/**
|
|
727
|
+
* @spfn/auth - Stored email normalization
|
|
728
|
+
*
|
|
729
|
+
* Brings existing rows into the canonical form the repository now writes and
|
|
730
|
+
* reads. Without it, normalizing the code alone would lock out every account
|
|
731
|
+
* whose address was stored with any capitalization: the lookup folds the typed
|
|
732
|
+
* address to lower case, the stored one stays mixed, and they stop matching.
|
|
733
|
+
*
|
|
734
|
+
* Runs once at startup and records that it ran, so a healthy install pays one
|
|
735
|
+
* small metadata read per boot rather than a scan of the users table.
|
|
736
|
+
*
|
|
737
|
+
* The work itself stays in the database — one query to find the addresses two
|
|
738
|
+
* rows would share, one statement to fold the rest. Nothing proportional to the
|
|
739
|
+
* table is carried through the application, which is what keeps a legacy
|
|
740
|
+
* install's first boot from turning into a row-at-a-time migration.
|
|
741
|
+
*/
|
|
742
|
+
interface EmailNormalizationResult {
|
|
743
|
+
/** Rows rewritten to canonical form. */
|
|
744
|
+
normalized: number;
|
|
745
|
+
/** User id groups that share an address once folded, left untouched. */
|
|
746
|
+
conflicts: number[][];
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Rewrite stored addresses to canonical form, skipping anything that would
|
|
750
|
+
* collide.
|
|
751
|
+
*
|
|
752
|
+
* A collision means two accounts differ only by capitalization. Which of them is
|
|
753
|
+
* the real one, and what happens to the other's data, is not a question this can
|
|
754
|
+
* answer — so every row in the group is left exactly as it is and all of their
|
|
755
|
+
* ids are reported. The alternative, merging or deleting one, destroys an
|
|
756
|
+
* account on a guess.
|
|
757
|
+
*
|
|
758
|
+
* @returns what was rewritten and what was left for an operator
|
|
759
|
+
*/
|
|
760
|
+
declare function normalizeStoredEmails(): Promise<EmailNormalizationResult>;
|
|
761
|
+
|
|
434
762
|
/**
|
|
435
763
|
* @spfn/auth - Permission Service
|
|
436
764
|
*
|
|
@@ -1944,9 +2272,10 @@ declare function sweepDuePurges(now?: Date): Promise<SweepDuePurgesResult>;
|
|
|
1944
2272
|
* list, and the row stores only the SHA-256 hash of the secret — the secret
|
|
1945
2273
|
* itself is shown once at issuance and never persisted.
|
|
1946
2274
|
*
|
|
1947
|
-
* Issuance is an operator act
|
|
1948
|
-
*
|
|
1949
|
-
*
|
|
2275
|
+
* Issuance is an operator act, and it goes over HTTP like everything else on
|
|
2276
|
+
* this surface: `POST /_auth/ops-tokens`, behind `authenticate` plus
|
|
2277
|
+
* `requireRole('admin', 'superadmin')`. `spfn ops token issue` signs in as an
|
|
2278
|
+
* administrator and calls it, so the CLI needs no database access of its own.
|
|
1950
2279
|
*/
|
|
1951
2280
|
declare const opsTokens: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
1952
2281
|
name: "ops_tokens";
|
|
@@ -2106,6 +2435,23 @@ type NewOpsToken = typeof opsTokens.$inferInsert;
|
|
|
2106
2435
|
* keeps).
|
|
2107
2436
|
*/
|
|
2108
2437
|
|
|
2438
|
+
/**
|
|
2439
|
+
* The shape every ops-token secret carries.
|
|
2440
|
+
*
|
|
2441
|
+
* Exported because an application that admits both an ops token and a user
|
|
2442
|
+
* session on one route has to tell the two apart before either is verified,
|
|
2443
|
+
* and a literal copied into application code drifts silently when this
|
|
2444
|
+
* package changes the shape.
|
|
2445
|
+
*/
|
|
2446
|
+
declare const OPS_TOKEN_PREFIX = "spfn_ops_";
|
|
2447
|
+
/**
|
|
2448
|
+
* Whether a presented bearer credential has the ops-token shape.
|
|
2449
|
+
*
|
|
2450
|
+
* Shape only — a token of this shape may still be unknown, revoked, or
|
|
2451
|
+
* expired; `verifyOpsTokenService` decides that. Callers read a raw header,
|
|
2452
|
+
* so a missing or non-string argument answers false rather than throwing.
|
|
2453
|
+
*/
|
|
2454
|
+
declare function isOpsToken(bearer: string): boolean;
|
|
2109
2455
|
/** What a verified token authorizes — the principal ops middleware exposes. */
|
|
2110
2456
|
interface VerifiedOpsToken {
|
|
2111
2457
|
tokenId: number;
|
|
@@ -2133,34 +2479,255 @@ declare function revokeOpsTokenService(id: number): Promise<OpsToken | null>;
|
|
|
2133
2479
|
declare function listOpsTokensService(): Promise<OpsToken[]>;
|
|
2134
2480
|
|
|
2135
2481
|
/**
|
|
2136
|
-
*
|
|
2482
|
+
* OAuth 2.1 Client Service
|
|
2483
|
+
*
|
|
2484
|
+
* RFC 7591 dynamic client registration, for public clients and nothing else.
|
|
2485
|
+
* `claude mcp add` and `codex mcp add` both begin here: the CLI discovers the
|
|
2486
|
+
* authorization server, registers itself unauthenticated, and gets back a
|
|
2487
|
+
* `client_id` it keeps on the user's disk.
|
|
2488
|
+
*
|
|
2489
|
+
* Unauthenticated registration means the table is a place anyone can write, so
|
|
2490
|
+
* two things bound it. The route rate-limits by IP, and this service caps how
|
|
2491
|
+
* many clients one IP may have standing that nobody has approved — a rate limit
|
|
2492
|
+
* alone would let an IP accumulate rows forever at a slow enough pace, because a
|
|
2493
|
+
* row costs nothing to make and lives until a job sweeps it. The cap looks back
|
|
2494
|
+
* an hour rather than over all time: it is a bound on how much junk one address
|
|
2495
|
+
* may have standing at once, not a lifetime quota on how many CLIs somebody may
|
|
2496
|
+
* ever connect.
|
|
2497
|
+
*
|
|
2498
|
+
* Every refusal here is RFC 7591 §3.2.2 shaped (`invalid_redirect_uri`,
|
|
2499
|
+
* `invalid_client_metadata`) rather than thrown, because the client reading it
|
|
2500
|
+
* is an OAuth client library that knows those two words and not this
|
|
2501
|
+
* application's error envelope. The route renders the shape; this decides it.
|
|
2502
|
+
*/
|
|
2503
|
+
/**
|
|
2504
|
+
* How many unapproved clients one IP may have standing.
|
|
2505
|
+
*
|
|
2506
|
+
* Generous against real use — a developer registering the same CLI on four
|
|
2507
|
+
* projects in an afternoon, each abandoned at the consent screen, is nowhere
|
|
2508
|
+
* near it — and small enough that the table cannot be filled from one address
|
|
2509
|
+
* between two sweeps of the purge job.
|
|
2510
|
+
*/
|
|
2511
|
+
declare const MAX_UNGRANTED_CLIENTS_PER_IP = 20;
|
|
2512
|
+
/** How long a client nobody approved is kept before the purge job deletes it. */
|
|
2513
|
+
declare const STALE_CLIENT_MAX_AGE_MS: number;
|
|
2514
|
+
/** The only grant types this authorization server issues. */
|
|
2515
|
+
declare const SUPPORTED_GRANT_TYPES: string[];
|
|
2516
|
+
/** The only response type. There is no implicit flow here. */
|
|
2517
|
+
declare const SUPPORTED_RESPONSE_TYPES: string[];
|
|
2518
|
+
/** Registration metadata as RFC 7591 names it, before anything is trusted. */
|
|
2519
|
+
interface OAuth2RegisterRequest {
|
|
2520
|
+
redirect_uris?: unknown;
|
|
2521
|
+
client_name?: unknown;
|
|
2522
|
+
token_endpoint_auth_method?: unknown;
|
|
2523
|
+
grant_types?: unknown;
|
|
2524
|
+
response_types?: unknown;
|
|
2525
|
+
}
|
|
2526
|
+
/** RFC 7591 §3.2.1 registration response. */
|
|
2527
|
+
interface OAuth2RegisteredClient {
|
|
2528
|
+
client_id: string;
|
|
2529
|
+
client_id_issued_at: number;
|
|
2530
|
+
client_name: string;
|
|
2531
|
+
redirect_uris: string[];
|
|
2532
|
+
token_endpoint_auth_method: string;
|
|
2533
|
+
grant_types: string[];
|
|
2534
|
+
response_types: string[];
|
|
2535
|
+
}
|
|
2536
|
+
/** A refusal in the shape the client's own OAuth library will read. */
|
|
2537
|
+
interface OAuth2RegisterRefusal {
|
|
2538
|
+
ok: false;
|
|
2539
|
+
status: 400 | 429;
|
|
2540
|
+
error: string;
|
|
2541
|
+
description: string;
|
|
2542
|
+
}
|
|
2543
|
+
type OAuth2RegisterResult = {
|
|
2544
|
+
ok: true;
|
|
2545
|
+
client: OAuth2RegisteredClient;
|
|
2546
|
+
} | OAuth2RegisterRefusal;
|
|
2547
|
+
/**
|
|
2548
|
+
* Register a public client.
|
|
2137
2549
|
*
|
|
2138
|
-
*
|
|
2550
|
+
* @param request - Metadata exactly as it arrived; nothing here is trusted
|
|
2551
|
+
* @param clientIp - Client IP for the standing cap, or null when unknowable
|
|
2139
2552
|
*/
|
|
2553
|
+
declare function registerOAuth2ClientService(request: OAuth2RegisterRequest, clientIp: string | null): Promise<OAuth2RegisterResult>;
|
|
2140
2554
|
/**
|
|
2141
|
-
*
|
|
2142
|
-
*
|
|
2555
|
+
* Delete clients nobody approved within a day — the `auth.oauth2.client-purge`
|
|
2556
|
+
* sweep.
|
|
2557
|
+
*
|
|
2558
|
+
* @returns how many rows the sweep removed
|
|
2143
2559
|
*/
|
|
2144
|
-
declare
|
|
2560
|
+
declare function purgeStaleOAuth2ClientsService(): Promise<{
|
|
2561
|
+
deleted: number;
|
|
2562
|
+
}>;
|
|
2145
2563
|
|
|
2146
2564
|
/**
|
|
2147
|
-
*
|
|
2565
|
+
* OAuth 2.1 Token Service
|
|
2566
|
+
*
|
|
2567
|
+
* The token endpoint's two grant types, and RFC 7009 revocation.
|
|
2568
|
+
*
|
|
2569
|
+
* Every failure on this endpoint answers `invalid_grant` — an unknown code, a
|
|
2570
|
+
* spent one, an expired one, a mismatched verifier, a code minted for another
|
|
2571
|
+
* client, a refresh token that never existed, one that was rotated away. That is
|
|
2572
|
+
* not laziness with error messages: the endpoint is public and unauthenticated,
|
|
2573
|
+
* and an error that distinguished "this code does not exist" from "this code
|
|
2574
|
+
* exists but is not yours" would answer the question an attacker holding a
|
|
2575
|
+
* stolen value is actually asking. `invalid_target` and `invalid_scope` are the
|
|
2576
|
+
* two exceptions, and both are decided from values the caller already knows.
|
|
2577
|
+
*
|
|
2578
|
+
* Nothing is spent or revoked before the presenter has shown it is the client the
|
|
2579
|
+
* credential belongs to. On the code path that means `client_id`, `redirect_uri`
|
|
2580
|
+
* and the PKCE verifier are all checked against the row before the code is
|
|
2581
|
+
* consumed — a caller that cannot satisfy them leaves the code unspent for the
|
|
2582
|
+
* real client and the grant untouched, because it has proved nothing about
|
|
2583
|
+
* itself and must therefore cost nobody anything.
|
|
2584
|
+
*
|
|
2585
|
+
* Two replays are detected rather than merely refused, and both revoke the whole
|
|
2586
|
+
* grant:
|
|
2587
|
+
*
|
|
2588
|
+
* - A code presented twice. The first exchange already produced tokens; if the
|
|
2589
|
+
* second presentation is the real client, somebody else holds those tokens.
|
|
2590
|
+
* - A rotated refresh token presented again. Same reasoning, and it is why a
|
|
2591
|
+
* rotated row is marked rather than deleted — a deleted row is
|
|
2592
|
+
* indistinguishable from one that never existed.
|
|
2593
|
+
*
|
|
2594
|
+
* Revoking the grant kills the replacement tokens too, which is the point: the
|
|
2595
|
+
* thief's pair and the client's pair both hang off the grant, and there is no
|
|
2596
|
+
* way to tell which is which.
|
|
2597
|
+
*/
|
|
2598
|
+
/** A token request as RFC 6749 names its fields, before anything is trusted. */
|
|
2599
|
+
interface OAuth2TokenRequest {
|
|
2600
|
+
grant_type?: string;
|
|
2601
|
+
code?: string;
|
|
2602
|
+
code_verifier?: string;
|
|
2603
|
+
client_id?: string;
|
|
2604
|
+
redirect_uri?: string;
|
|
2605
|
+
refresh_token?: string;
|
|
2606
|
+
resource?: string;
|
|
2607
|
+
scope?: string;
|
|
2608
|
+
}
|
|
2609
|
+
/** RFC 6749 §5.1, with `scope` always stated so a narrowed request is legible. */
|
|
2610
|
+
interface OAuth2TokenResponse {
|
|
2611
|
+
access_token: string;
|
|
2612
|
+
token_type: 'Bearer';
|
|
2613
|
+
expires_in: number;
|
|
2614
|
+
refresh_token: string;
|
|
2615
|
+
scope: string;
|
|
2616
|
+
}
|
|
2617
|
+
/** RFC 6749 §5.2: the code, and a sentence for whoever is reading the logs. */
|
|
2618
|
+
interface OAuth2TokenRefusal {
|
|
2619
|
+
ok: false;
|
|
2620
|
+
error: string;
|
|
2621
|
+
description: string;
|
|
2622
|
+
}
|
|
2623
|
+
type OAuth2TokenResult = {
|
|
2624
|
+
ok: true;
|
|
2625
|
+
tokens: OAuth2TokenResponse;
|
|
2626
|
+
} | OAuth2TokenRefusal;
|
|
2627
|
+
/**
|
|
2628
|
+
* Exchange a code or refresh a token.
|
|
2148
2629
|
*
|
|
2149
|
-
*
|
|
2630
|
+
* @param request - The form or JSON body, exactly as it arrived
|
|
2631
|
+
*/
|
|
2632
|
+
declare function oauth2TokenService(request: OAuth2TokenRequest): Promise<OAuth2TokenResult>;
|
|
2633
|
+
/**
|
|
2634
|
+
* Revoke a presented token.
|
|
2150
2635
|
*
|
|
2151
|
-
*
|
|
2152
|
-
*
|
|
2153
|
-
*
|
|
2154
|
-
*
|
|
2155
|
-
*
|
|
2156
|
-
*
|
|
2636
|
+
* Answers nothing, ever. RFC 7009 §2.2 requires 200 for a token that was never
|
|
2637
|
+
* issued as well as for one that was, so a caller cannot use this endpoint to
|
|
2638
|
+
* ask whether a value it found somewhere is real.
|
|
2639
|
+
*
|
|
2640
|
+
* A refresh token takes the grant with it: a client presenting its refresh token
|
|
2641
|
+
* here is saying it is finished, and leaving that grant's access tokens alive
|
|
2642
|
+
* for eight more hours would be honouring half the request. An access token is
|
|
2643
|
+
* revoked alone — a client may be discarding one it no longer needs while still
|
|
2644
|
+
* holding the connection.
|
|
2645
|
+
*
|
|
2646
|
+
* A `client_id` that is not the token's is the same 200 and revokes nothing
|
|
2647
|
+
* (RFC 7009 §2.1). The endpoint has no client authentication to lean on, so this
|
|
2648
|
+
* does not stop anybody who holds the token from revoking it — what it stops is
|
|
2649
|
+
* one client tearing down another's connection by presenting a value it came
|
|
2650
|
+
* across, which is the only thing a public client's id can be asked to mean.
|
|
2651
|
+
* Which is also why it is required and not optional: RFC 7009 §2.1 has the
|
|
2652
|
+
* client authenticate per RFC 6749 §2.3, and §2.3.1 says a public client with no
|
|
2653
|
+
* credentials identifies itself with `client_id`. The route refuses a request
|
|
2654
|
+
* that carries none before this is called.
|
|
2655
|
+
*
|
|
2656
|
+
* @param token - The value to revoke, access or refresh
|
|
2657
|
+
* @param clientId - Checked against the token's client
|
|
2157
2658
|
*/
|
|
2158
|
-
declare
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2659
|
+
declare function revokeOAuth2TokenService(token: string, clientId: string): Promise<void>;
|
|
2660
|
+
|
|
2661
|
+
/**
|
|
2662
|
+
* OAuth 2.1 Access Token Verification
|
|
2663
|
+
*
|
|
2664
|
+
* `verifyAccessToken` is what `@spfn/mcp` calls on every request to `/mcp`. It
|
|
2665
|
+
* is the one function in this feature an application wires up by hand:
|
|
2666
|
+
*
|
|
2667
|
+
* ```typescript
|
|
2668
|
+
* createMcpRoute({ validateToken: verifyAccessToken })
|
|
2669
|
+
* ```
|
|
2670
|
+
*
|
|
2671
|
+
* Null for every refusal, and the same null for all of them — unknown, revoked,
|
|
2672
|
+
* expired, refresh-kind, grant revoked, wrong resource. The non-disclosure rule
|
|
2673
|
+
* `verifyOpsTokenService` keeps, for the same reason: this is a public endpoint
|
|
2674
|
+
* and the refusal must not tell a caller whether the value it presented was ever
|
|
2675
|
+
* real.
|
|
2676
|
+
*
|
|
2677
|
+
* The resource check is not a formality. An access token minted for one API is
|
|
2678
|
+
* refused by another (RFC 8707), so a token a user approved for their MCP server
|
|
2679
|
+
* cannot be replayed against a neighbouring deployment that happens to share this
|
|
2680
|
+
* authorization server.
|
|
2681
|
+
*/
|
|
2682
|
+
/** What a verified access token authorizes. */
|
|
2683
|
+
interface VerifiedOAuth2AccessToken {
|
|
2684
|
+
/** The public `client_id` of the CLI holding the token. */
|
|
2685
|
+
clientId: string;
|
|
2686
|
+
/** Scope names, as granted or as the refresh that minted it narrowed them to. */
|
|
2687
|
+
scopes: string[];
|
|
2688
|
+
/** Expiry as seconds since the epoch — the unit every OAuth field uses. */
|
|
2689
|
+
expiresAt: number;
|
|
2690
|
+
/** The account the CLI is acting for. */
|
|
2691
|
+
userId: string;
|
|
2692
|
+
}
|
|
2693
|
+
/**
|
|
2694
|
+
* Verify a bearer token presented to a protected resource.
|
|
2695
|
+
*
|
|
2696
|
+
* @param token - The raw bearer value
|
|
2697
|
+
* @param resource - The resource being accessed, e.g. `https://api.example.com/mcp`
|
|
2698
|
+
* @returns the principal, or null for any reason at all
|
|
2699
|
+
*/
|
|
2700
|
+
declare function verifyAccessToken(token: string, resource: string): Promise<VerifiedOAuth2AccessToken | null>;
|
|
2701
|
+
|
|
2702
|
+
/**
|
|
2703
|
+
* @spfn/auth - Database Schema Definition
|
|
2704
|
+
*
|
|
2705
|
+
* Defines the 'spfn_auth' PostgreSQL schema for all auth-related tables
|
|
2706
|
+
*/
|
|
2707
|
+
/**
|
|
2708
|
+
* Auth schema for all authentication and authorization tables
|
|
2709
|
+
* Tables: users, roles, permissions, user_invitations, etc.
|
|
2710
|
+
*/
|
|
2711
|
+
declare const authSchema: drizzle_orm_pg_core.PgSchema<string>;
|
|
2712
|
+
|
|
2713
|
+
/**
|
|
2714
|
+
* @spfn/auth - User Profiles Entity
|
|
2715
|
+
*
|
|
2716
|
+
* User profile information (separate from authentication data)
|
|
2717
|
+
*
|
|
2718
|
+
* Features:
|
|
2719
|
+
* - Display name and personal information
|
|
2720
|
+
* - Avatar and bio
|
|
2721
|
+
* - Locale and timezone preferences
|
|
2722
|
+
* - Optional demographic data
|
|
2723
|
+
* - Social links and professional info
|
|
2724
|
+
*/
|
|
2725
|
+
declare const userProfiles: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
2726
|
+
name: "user_profiles";
|
|
2727
|
+
schema: string;
|
|
2728
|
+
columns: {
|
|
2729
|
+
createdAt: drizzle_orm_pg_core.PgBuildColumn<"user_profiles", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>, {
|
|
2730
|
+
name: string;
|
|
2164
2731
|
tableName: "user_profiles";
|
|
2165
2732
|
dataType: "object date";
|
|
2166
2733
|
data: Date;
|
|
@@ -3057,23 +3624,25 @@ type VerificationCode = typeof verificationCodes.$inferSelect;
|
|
|
3057
3624
|
type NewVerificationCode = typeof verificationCodes.$inferInsert;
|
|
3058
3625
|
|
|
3059
3626
|
/**
|
|
3060
|
-
* @spfn/auth -
|
|
3627
|
+
* @spfn/auth - Signup Link Tokens Entity
|
|
3061
3628
|
*
|
|
3062
|
-
*
|
|
3629
|
+
* Backs the verified-email signup flow: a one-time confirmation link is emailed,
|
|
3630
|
+
* opening it mints a short-lived setup session, and setting a password on that
|
|
3631
|
+
* session creates the account.
|
|
3063
3632
|
*
|
|
3064
|
-
*
|
|
3065
|
-
* -
|
|
3066
|
-
*
|
|
3067
|
-
*
|
|
3068
|
-
* -
|
|
3633
|
+
* A separate table from `verification_codes` on purpose. That table stores its
|
|
3634
|
+
* six-digit code in plaintext, which is acceptable for a short-lived numeric code
|
|
3635
|
+
* bounded by an attempt counter, but a link token is a bearer credential sitting
|
|
3636
|
+
* in someone's mailbox and must only ever be stored as a hash. Sharing one table
|
|
3637
|
+
* would make "plaintext or hash" a per-row property that nothing enforces.
|
|
3069
3638
|
*/
|
|
3070
|
-
declare const
|
|
3071
|
-
name: "
|
|
3639
|
+
declare const signupLinkTokens: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
3640
|
+
name: "signup_link_tokens";
|
|
3072
3641
|
schema: string;
|
|
3073
3642
|
columns: {
|
|
3074
|
-
createdAt: drizzle_orm_pg_core.PgBuildColumn<"
|
|
3643
|
+
createdAt: drizzle_orm_pg_core.PgBuildColumn<"signup_link_tokens", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>, {
|
|
3075
3644
|
name: string;
|
|
3076
|
-
tableName: "
|
|
3645
|
+
tableName: "signup_link_tokens";
|
|
3077
3646
|
dataType: "object date";
|
|
3078
3647
|
data: Date;
|
|
3079
3648
|
driverParam: string;
|
|
@@ -3086,9 +3655,9 @@ declare const permissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3086
3655
|
identity: undefined;
|
|
3087
3656
|
generated: undefined;
|
|
3088
3657
|
}>;
|
|
3089
|
-
updatedAt: drizzle_orm_pg_core.PgBuildColumn<"
|
|
3658
|
+
updatedAt: drizzle_orm_pg_core.PgBuildColumn<"signup_link_tokens", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>>, {
|
|
3090
3659
|
name: string;
|
|
3091
|
-
tableName: "
|
|
3660
|
+
tableName: "signup_link_tokens";
|
|
3092
3661
|
dataType: "object date";
|
|
3093
3662
|
data: Date;
|
|
3094
3663
|
driverParam: string;
|
|
@@ -3101,9 +3670,9 @@ declare const permissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3101
3670
|
identity: undefined;
|
|
3102
3671
|
generated: undefined;
|
|
3103
3672
|
}>;
|
|
3104
|
-
id: drizzle_orm_pg_core.PgBuildColumn<"
|
|
3673
|
+
id: drizzle_orm_pg_core.PgBuildColumn<"signup_link_tokens", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>, {
|
|
3105
3674
|
name: string;
|
|
3106
|
-
tableName: "
|
|
3675
|
+
tableName: "signup_link_tokens";
|
|
3107
3676
|
dataType: "number int53";
|
|
3108
3677
|
data: number;
|
|
3109
3678
|
driverParam: number;
|
|
@@ -3116,9 +3685,9 @@ declare const permissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3116
3685
|
identity: undefined;
|
|
3117
3686
|
generated: undefined;
|
|
3118
3687
|
}>;
|
|
3119
|
-
|
|
3688
|
+
email: drizzle_orm_pg_core.PgBuildColumn<"signup_link_tokens", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
3120
3689
|
name: string;
|
|
3121
|
-
tableName: "
|
|
3690
|
+
tableName: "signup_link_tokens";
|
|
3122
3691
|
dataType: "string";
|
|
3123
3692
|
data: string;
|
|
3124
3693
|
driverParam: string;
|
|
@@ -3131,13 +3700,13 @@ declare const permissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3131
3700
|
identity: undefined;
|
|
3132
3701
|
generated: undefined;
|
|
3133
3702
|
}>;
|
|
3134
|
-
|
|
3703
|
+
tokenHash: drizzle_orm_pg_core.PgBuildColumn<"signup_link_tokens", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
|
|
3135
3704
|
name: string;
|
|
3136
|
-
tableName: "
|
|
3705
|
+
tableName: "signup_link_tokens";
|
|
3137
3706
|
dataType: "string";
|
|
3138
3707
|
data: string;
|
|
3139
3708
|
driverParam: string;
|
|
3140
|
-
notNull:
|
|
3709
|
+
notNull: false;
|
|
3141
3710
|
hasDefault: false;
|
|
3142
3711
|
isPrimaryKey: false;
|
|
3143
3712
|
isAutoincrement: false;
|
|
@@ -3146,9 +3715,9 @@ declare const permissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3146
3715
|
identity: undefined;
|
|
3147
3716
|
generated: undefined;
|
|
3148
3717
|
}>;
|
|
3149
|
-
|
|
3718
|
+
returnPath: drizzle_orm_pg_core.PgBuildColumn<"signup_link_tokens", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
|
|
3150
3719
|
name: string;
|
|
3151
|
-
tableName: "
|
|
3720
|
+
tableName: "signup_link_tokens";
|
|
3152
3721
|
dataType: "string";
|
|
3153
3722
|
data: string;
|
|
3154
3723
|
driverParam: string;
|
|
@@ -3161,29 +3730,44 @@ declare const permissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3161
3730
|
identity: undefined;
|
|
3162
3731
|
generated: undefined;
|
|
3163
3732
|
}>;
|
|
3164
|
-
|
|
3733
|
+
expiresAt: drizzle_orm_pg_core.PgBuildColumn<"signup_link_tokens", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTimestampBuilder>, {
|
|
3165
3734
|
name: string;
|
|
3166
|
-
tableName: "
|
|
3167
|
-
dataType: "
|
|
3168
|
-
data:
|
|
3735
|
+
tableName: "signup_link_tokens";
|
|
3736
|
+
dataType: "object date";
|
|
3737
|
+
data: Date;
|
|
3738
|
+
driverParam: string;
|
|
3739
|
+
notNull: true;
|
|
3740
|
+
hasDefault: false;
|
|
3741
|
+
isPrimaryKey: false;
|
|
3742
|
+
isAutoincrement: false;
|
|
3743
|
+
hasRuntimeDefault: false;
|
|
3744
|
+
enumValues: undefined;
|
|
3745
|
+
identity: undefined;
|
|
3746
|
+
generated: undefined;
|
|
3747
|
+
}>;
|
|
3748
|
+
consumedAt: drizzle_orm_pg_core.PgBuildColumn<"signup_link_tokens", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
3749
|
+
name: string;
|
|
3750
|
+
tableName: "signup_link_tokens";
|
|
3751
|
+
dataType: "object date";
|
|
3752
|
+
data: Date;
|
|
3169
3753
|
driverParam: string;
|
|
3170
3754
|
notNull: false;
|
|
3171
3755
|
hasDefault: false;
|
|
3172
3756
|
isPrimaryKey: false;
|
|
3173
3757
|
isAutoincrement: false;
|
|
3174
3758
|
hasRuntimeDefault: false;
|
|
3175
|
-
enumValues:
|
|
3759
|
+
enumValues: undefined;
|
|
3176
3760
|
identity: undefined;
|
|
3177
3761
|
generated: undefined;
|
|
3178
3762
|
}>;
|
|
3179
|
-
|
|
3763
|
+
supersededAt: drizzle_orm_pg_core.PgBuildColumn<"signup_link_tokens", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
3180
3764
|
name: string;
|
|
3181
|
-
tableName: "
|
|
3182
|
-
dataType: "
|
|
3183
|
-
data:
|
|
3184
|
-
driverParam:
|
|
3185
|
-
notNull:
|
|
3186
|
-
hasDefault:
|
|
3765
|
+
tableName: "signup_link_tokens";
|
|
3766
|
+
dataType: "object date";
|
|
3767
|
+
data: Date;
|
|
3768
|
+
driverParam: string;
|
|
3769
|
+
notNull: false;
|
|
3770
|
+
hasDefault: false;
|
|
3187
3771
|
isPrimaryKey: false;
|
|
3188
3772
|
isAutoincrement: false;
|
|
3189
3773
|
hasRuntimeDefault: false;
|
|
@@ -3191,14 +3775,14 @@ declare const permissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3191
3775
|
identity: undefined;
|
|
3192
3776
|
generated: undefined;
|
|
3193
3777
|
}>;
|
|
3194
|
-
|
|
3778
|
+
setupSecretHash: drizzle_orm_pg_core.PgBuildColumn<"signup_link_tokens", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
|
|
3195
3779
|
name: string;
|
|
3196
|
-
tableName: "
|
|
3197
|
-
dataType: "
|
|
3198
|
-
data:
|
|
3199
|
-
driverParam:
|
|
3200
|
-
notNull:
|
|
3201
|
-
hasDefault:
|
|
3780
|
+
tableName: "signup_link_tokens";
|
|
3781
|
+
dataType: "string";
|
|
3782
|
+
data: string;
|
|
3783
|
+
driverParam: string;
|
|
3784
|
+
notNull: false;
|
|
3785
|
+
hasDefault: false;
|
|
3202
3786
|
isPrimaryKey: false;
|
|
3203
3787
|
isAutoincrement: false;
|
|
3204
3788
|
hasRuntimeDefault: false;
|
|
@@ -3206,14 +3790,14 @@ declare const permissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3206
3790
|
identity: undefined;
|
|
3207
3791
|
generated: undefined;
|
|
3208
3792
|
}>;
|
|
3209
|
-
|
|
3793
|
+
setupExpiresAt: drizzle_orm_pg_core.PgBuildColumn<"signup_link_tokens", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
3210
3794
|
name: string;
|
|
3211
|
-
tableName: "
|
|
3212
|
-
dataType: "
|
|
3213
|
-
data:
|
|
3214
|
-
driverParam:
|
|
3215
|
-
notNull:
|
|
3216
|
-
hasDefault:
|
|
3795
|
+
tableName: "signup_link_tokens";
|
|
3796
|
+
dataType: "object date";
|
|
3797
|
+
data: Date;
|
|
3798
|
+
driverParam: string;
|
|
3799
|
+
notNull: false;
|
|
3800
|
+
hasDefault: false;
|
|
3217
3801
|
isPrimaryKey: false;
|
|
3218
3802
|
isAutoincrement: false;
|
|
3219
3803
|
hasRuntimeDefault: false;
|
|
@@ -3221,12 +3805,12 @@ declare const permissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3221
3805
|
identity: undefined;
|
|
3222
3806
|
generated: undefined;
|
|
3223
3807
|
}>;
|
|
3224
|
-
|
|
3808
|
+
completedAt: drizzle_orm_pg_core.PgBuildColumn<"signup_link_tokens", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
3225
3809
|
name: string;
|
|
3226
|
-
tableName: "
|
|
3227
|
-
dataType: "object
|
|
3228
|
-
data:
|
|
3229
|
-
driverParam:
|
|
3810
|
+
tableName: "signup_link_tokens";
|
|
3811
|
+
dataType: "object date";
|
|
3812
|
+
data: Date;
|
|
3813
|
+
driverParam: string;
|
|
3230
3814
|
notNull: false;
|
|
3231
3815
|
hasDefault: false;
|
|
3232
3816
|
isPrimaryKey: false;
|
|
@@ -3239,27 +3823,35 @@ declare const permissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3239
3823
|
};
|
|
3240
3824
|
dialect: "pg";
|
|
3241
3825
|
}>;
|
|
3242
|
-
type
|
|
3243
|
-
type
|
|
3244
|
-
type Permission = PermissionEntity;
|
|
3245
|
-
type NewPermission = NewPermissionEntity;
|
|
3826
|
+
type SignupLinkToken = typeof signupLinkTokens.$inferSelect;
|
|
3827
|
+
type NewSignupLinkToken = typeof signupLinkTokens.$inferInsert;
|
|
3246
3828
|
|
|
3247
3829
|
/**
|
|
3248
|
-
* @spfn/auth -
|
|
3830
|
+
* @spfn/auth - Password Reset Tokens Entity
|
|
3249
3831
|
*
|
|
3250
|
-
*
|
|
3832
|
+
* Backs the password reset flow: a one-time link is emailed to an address the
|
|
3833
|
+
* account has already proved, opening it mints a short-lived setup session, and
|
|
3834
|
+
* setting a password on that session replaces the old one and signs the browser
|
|
3835
|
+
* in.
|
|
3251
3836
|
*
|
|
3252
|
-
*
|
|
3253
|
-
*
|
|
3254
|
-
*
|
|
3837
|
+
* A separate table from `signup_link_tokens` even though the two carry the same
|
|
3838
|
+
* columns. They are the same shape, not the same thing: one names an address
|
|
3839
|
+
* that has no account and the other names a user that does, and the setup
|
|
3840
|
+
* secrets they mint must not be interchangeable. Sharing one table would make
|
|
3841
|
+
* "which flow is this row" a per-row property that the lookup would have to
|
|
3842
|
+
* remember to filter on — and a lookup that forgets would accept a signup secret
|
|
3843
|
+
* as a reset secret.
|
|
3844
|
+
*
|
|
3845
|
+
* The link token and the setup secret are bearer credentials, so only their
|
|
3846
|
+
* SHA-256 hashes are stored.
|
|
3255
3847
|
*/
|
|
3256
|
-
declare const
|
|
3257
|
-
name: "
|
|
3848
|
+
declare const passwordResetTokens: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
3849
|
+
name: "password_reset_tokens";
|
|
3258
3850
|
schema: string;
|
|
3259
3851
|
columns: {
|
|
3260
|
-
createdAt: drizzle_orm_pg_core.PgBuildColumn<"
|
|
3852
|
+
createdAt: drizzle_orm_pg_core.PgBuildColumn<"password_reset_tokens", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>, {
|
|
3261
3853
|
name: string;
|
|
3262
|
-
tableName: "
|
|
3854
|
+
tableName: "password_reset_tokens";
|
|
3263
3855
|
dataType: "object date";
|
|
3264
3856
|
data: Date;
|
|
3265
3857
|
driverParam: string;
|
|
@@ -3272,9 +3864,9 @@ declare const rolePermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3272
3864
|
identity: undefined;
|
|
3273
3865
|
generated: undefined;
|
|
3274
3866
|
}>;
|
|
3275
|
-
updatedAt: drizzle_orm_pg_core.PgBuildColumn<"
|
|
3867
|
+
updatedAt: drizzle_orm_pg_core.PgBuildColumn<"password_reset_tokens", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>>, {
|
|
3276
3868
|
name: string;
|
|
3277
|
-
tableName: "
|
|
3869
|
+
tableName: "password_reset_tokens";
|
|
3278
3870
|
dataType: "object date";
|
|
3279
3871
|
data: Date;
|
|
3280
3872
|
driverParam: string;
|
|
@@ -3287,9 +3879,9 @@ declare const rolePermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3287
3879
|
identity: undefined;
|
|
3288
3880
|
generated: undefined;
|
|
3289
3881
|
}>;
|
|
3290
|
-
id: drizzle_orm_pg_core.PgBuildColumn<"
|
|
3882
|
+
id: drizzle_orm_pg_core.PgBuildColumn<"password_reset_tokens", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>, {
|
|
3291
3883
|
name: string;
|
|
3292
|
-
tableName: "
|
|
3884
|
+
tableName: "password_reset_tokens";
|
|
3293
3885
|
dataType: "number int53";
|
|
3294
3886
|
data: number;
|
|
3295
3887
|
driverParam: number;
|
|
@@ -3302,9 +3894,9 @@ declare const rolePermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3302
3894
|
identity: undefined;
|
|
3303
3895
|
generated: undefined;
|
|
3304
3896
|
}>;
|
|
3305
|
-
|
|
3897
|
+
userId: drizzle_orm_pg_core.PgBuildColumn<"password_reset_tokens", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>, {
|
|
3306
3898
|
name: string;
|
|
3307
|
-
tableName: "
|
|
3899
|
+
tableName: "password_reset_tokens";
|
|
3308
3900
|
dataType: "number int53";
|
|
3309
3901
|
data: number;
|
|
3310
3902
|
driverParam: string | number;
|
|
@@ -3317,12 +3909,12 @@ declare const rolePermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3317
3909
|
identity: undefined;
|
|
3318
3910
|
generated: undefined;
|
|
3319
3911
|
}>;
|
|
3320
|
-
|
|
3912
|
+
email: drizzle_orm_pg_core.PgBuildColumn<"password_reset_tokens", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
3321
3913
|
name: string;
|
|
3322
|
-
tableName: "
|
|
3323
|
-
dataType: "
|
|
3324
|
-
data:
|
|
3325
|
-
driverParam: string
|
|
3914
|
+
tableName: "password_reset_tokens";
|
|
3915
|
+
dataType: "string";
|
|
3916
|
+
data: string;
|
|
3917
|
+
driverParam: string;
|
|
3326
3918
|
notNull: true;
|
|
3327
3919
|
hasDefault: false;
|
|
3328
3920
|
isPrimaryKey: false;
|
|
@@ -3332,38 +3924,14 @@ declare const rolePermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3332
3924
|
identity: undefined;
|
|
3333
3925
|
generated: undefined;
|
|
3334
3926
|
}>;
|
|
3335
|
-
|
|
3336
|
-
dialect: "pg";
|
|
3337
|
-
}>;
|
|
3338
|
-
type RolePermission = typeof rolePermissions.$inferSelect;
|
|
3339
|
-
type NewRolePermission = typeof rolePermissions.$inferInsert;
|
|
3340
|
-
|
|
3341
|
-
/**
|
|
3342
|
-
* @spfn/auth - User-Permissions Override Entity
|
|
3343
|
-
*
|
|
3344
|
-
* Per-user permission grants/revocations
|
|
3345
|
-
*
|
|
3346
|
-
* Features:
|
|
3347
|
-
* - Grant additional permissions to specific users
|
|
3348
|
-
* - Revoke role-inherited permissions from specific users
|
|
3349
|
-
* - Temporary permissions with expiration
|
|
3350
|
-
* - Audit trail with reason field
|
|
3351
|
-
*
|
|
3352
|
-
* Priority:
|
|
3353
|
-
* User permissions override role permissions
|
|
3354
|
-
*/
|
|
3355
|
-
declare const userPermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
3356
|
-
name: "user_permissions";
|
|
3357
|
-
schema: string;
|
|
3358
|
-
columns: {
|
|
3359
|
-
createdAt: drizzle_orm_pg_core.PgBuildColumn<"user_permissions", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>, {
|
|
3927
|
+
tokenHash: drizzle_orm_pg_core.PgBuildColumn<"password_reset_tokens", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
|
|
3360
3928
|
name: string;
|
|
3361
|
-
tableName: "
|
|
3362
|
-
dataType: "
|
|
3363
|
-
data:
|
|
3929
|
+
tableName: "password_reset_tokens";
|
|
3930
|
+
dataType: "string";
|
|
3931
|
+
data: string;
|
|
3364
3932
|
driverParam: string;
|
|
3365
|
-
notNull:
|
|
3366
|
-
hasDefault:
|
|
3933
|
+
notNull: false;
|
|
3934
|
+
hasDefault: false;
|
|
3367
3935
|
isPrimaryKey: false;
|
|
3368
3936
|
isAutoincrement: false;
|
|
3369
3937
|
hasRuntimeDefault: false;
|
|
@@ -3371,14 +3939,14 @@ declare const userPermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3371
3939
|
identity: undefined;
|
|
3372
3940
|
generated: undefined;
|
|
3373
3941
|
}>;
|
|
3374
|
-
|
|
3942
|
+
returnPath: drizzle_orm_pg_core.PgBuildColumn<"password_reset_tokens", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
|
|
3375
3943
|
name: string;
|
|
3376
|
-
tableName: "
|
|
3377
|
-
dataType: "
|
|
3378
|
-
data:
|
|
3944
|
+
tableName: "password_reset_tokens";
|
|
3945
|
+
dataType: "string";
|
|
3946
|
+
data: string;
|
|
3379
3947
|
driverParam: string;
|
|
3380
|
-
notNull:
|
|
3381
|
-
hasDefault:
|
|
3948
|
+
notNull: false;
|
|
3949
|
+
hasDefault: false;
|
|
3382
3950
|
isPrimaryKey: false;
|
|
3383
3951
|
isAutoincrement: false;
|
|
3384
3952
|
hasRuntimeDefault: false;
|
|
@@ -3386,14 +3954,14 @@ declare const userPermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3386
3954
|
identity: undefined;
|
|
3387
3955
|
generated: undefined;
|
|
3388
3956
|
}>;
|
|
3389
|
-
|
|
3957
|
+
expiresAt: drizzle_orm_pg_core.PgBuildColumn<"password_reset_tokens", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTimestampBuilder>, {
|
|
3390
3958
|
name: string;
|
|
3391
|
-
tableName: "
|
|
3392
|
-
dataType: "
|
|
3393
|
-
data:
|
|
3394
|
-
driverParam:
|
|
3959
|
+
tableName: "password_reset_tokens";
|
|
3960
|
+
dataType: "object date";
|
|
3961
|
+
data: Date;
|
|
3962
|
+
driverParam: string;
|
|
3395
3963
|
notNull: true;
|
|
3396
|
-
hasDefault:
|
|
3964
|
+
hasDefault: false;
|
|
3397
3965
|
isPrimaryKey: false;
|
|
3398
3966
|
isAutoincrement: false;
|
|
3399
3967
|
hasRuntimeDefault: false;
|
|
@@ -3401,13 +3969,13 @@ declare const userPermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3401
3969
|
identity: undefined;
|
|
3402
3970
|
generated: undefined;
|
|
3403
3971
|
}>;
|
|
3404
|
-
|
|
3972
|
+
consumedAt: drizzle_orm_pg_core.PgBuildColumn<"password_reset_tokens", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
3405
3973
|
name: string;
|
|
3406
|
-
tableName: "
|
|
3407
|
-
dataType: "
|
|
3408
|
-
data:
|
|
3409
|
-
driverParam: string
|
|
3410
|
-
notNull:
|
|
3974
|
+
tableName: "password_reset_tokens";
|
|
3975
|
+
dataType: "object date";
|
|
3976
|
+
data: Date;
|
|
3977
|
+
driverParam: string;
|
|
3978
|
+
notNull: false;
|
|
3411
3979
|
hasDefault: false;
|
|
3412
3980
|
isPrimaryKey: false;
|
|
3413
3981
|
isAutoincrement: false;
|
|
@@ -3416,13 +3984,13 @@ declare const userPermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3416
3984
|
identity: undefined;
|
|
3417
3985
|
generated: undefined;
|
|
3418
3986
|
}>;
|
|
3419
|
-
|
|
3987
|
+
supersededAt: drizzle_orm_pg_core.PgBuildColumn<"password_reset_tokens", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
3420
3988
|
name: string;
|
|
3421
|
-
tableName: "
|
|
3422
|
-
dataType: "
|
|
3423
|
-
data:
|
|
3424
|
-
driverParam: string
|
|
3425
|
-
notNull:
|
|
3989
|
+
tableName: "password_reset_tokens";
|
|
3990
|
+
dataType: "object date";
|
|
3991
|
+
data: Date;
|
|
3992
|
+
driverParam: string;
|
|
3993
|
+
notNull: false;
|
|
3426
3994
|
hasDefault: false;
|
|
3427
3995
|
isPrimaryKey: false;
|
|
3428
3996
|
isAutoincrement: false;
|
|
@@ -3431,14 +3999,14 @@ declare const userPermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3431
3999
|
identity: undefined;
|
|
3432
4000
|
generated: undefined;
|
|
3433
4001
|
}>;
|
|
3434
|
-
|
|
4002
|
+
setupSecretHash: drizzle_orm_pg_core.PgBuildColumn<"password_reset_tokens", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
|
|
3435
4003
|
name: string;
|
|
3436
|
-
tableName: "
|
|
3437
|
-
dataType: "
|
|
3438
|
-
data:
|
|
3439
|
-
driverParam:
|
|
3440
|
-
notNull:
|
|
3441
|
-
hasDefault:
|
|
4004
|
+
tableName: "password_reset_tokens";
|
|
4005
|
+
dataType: "string";
|
|
4006
|
+
data: string;
|
|
4007
|
+
driverParam: string;
|
|
4008
|
+
notNull: false;
|
|
4009
|
+
hasDefault: false;
|
|
3442
4010
|
isPrimaryKey: false;
|
|
3443
4011
|
isAutoincrement: false;
|
|
3444
4012
|
hasRuntimeDefault: false;
|
|
@@ -3446,11 +4014,11 @@ declare const userPermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3446
4014
|
identity: undefined;
|
|
3447
4015
|
generated: undefined;
|
|
3448
4016
|
}>;
|
|
3449
|
-
|
|
4017
|
+
setupExpiresAt: drizzle_orm_pg_core.PgBuildColumn<"password_reset_tokens", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
3450
4018
|
name: string;
|
|
3451
|
-
tableName: "
|
|
3452
|
-
dataType: "
|
|
3453
|
-
data:
|
|
4019
|
+
tableName: "password_reset_tokens";
|
|
4020
|
+
dataType: "object date";
|
|
4021
|
+
data: Date;
|
|
3454
4022
|
driverParam: string;
|
|
3455
4023
|
notNull: false;
|
|
3456
4024
|
hasDefault: false;
|
|
@@ -3461,9 +4029,859 @@ declare const userPermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3461
4029
|
identity: undefined;
|
|
3462
4030
|
generated: undefined;
|
|
3463
4031
|
}>;
|
|
3464
|
-
|
|
4032
|
+
completedAt: drizzle_orm_pg_core.PgBuildColumn<"password_reset_tokens", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
3465
4033
|
name: string;
|
|
3466
|
-
tableName: "
|
|
4034
|
+
tableName: "password_reset_tokens";
|
|
4035
|
+
dataType: "object date";
|
|
4036
|
+
data: Date;
|
|
4037
|
+
driverParam: string;
|
|
4038
|
+
notNull: false;
|
|
4039
|
+
hasDefault: false;
|
|
4040
|
+
isPrimaryKey: false;
|
|
4041
|
+
isAutoincrement: false;
|
|
4042
|
+
hasRuntimeDefault: false;
|
|
4043
|
+
enumValues: undefined;
|
|
4044
|
+
identity: undefined;
|
|
4045
|
+
generated: undefined;
|
|
4046
|
+
}>;
|
|
4047
|
+
};
|
|
4048
|
+
dialect: "pg";
|
|
4049
|
+
}>;
|
|
4050
|
+
type PasswordResetToken = typeof passwordResetTokens.$inferSelect;
|
|
4051
|
+
type NewPasswordResetToken = typeof passwordResetTokens.$inferInsert;
|
|
4052
|
+
|
|
4053
|
+
/**
|
|
4054
|
+
* @spfn/auth - WebAuthn Challenges Entity
|
|
4055
|
+
*
|
|
4056
|
+
* The random value an authenticator signs, parked between the `options` call
|
|
4057
|
+
* that minted it and the `verify` call that spends it.
|
|
4058
|
+
*
|
|
4059
|
+
* In the database rather than in a cookie or in memory, for two reasons: the
|
|
4060
|
+
* check that makes a challenge one-time has to be a conditional UPDATE that two
|
|
4061
|
+
* concurrent verifies cannot both win, and several API instances serve the same
|
|
4062
|
+
* browser — an in-process map would let a replay land on the instance that never
|
|
4063
|
+
* saw the first attempt.
|
|
4064
|
+
*
|
|
4065
|
+
* Only the SHA-256 of the challenge is stored, following the rule the package
|
|
4066
|
+
* already applies to signup link tokens. A challenge is a nonce rather than a
|
|
4067
|
+
* bearer credential, so this buys less than it does there; it costs nothing, and
|
|
4068
|
+
* it keeps "plaintext or hash" from becoming a per-table judgement call.
|
|
4069
|
+
*/
|
|
4070
|
+
/**
|
|
4071
|
+
* Which ceremony the challenge was minted for.
|
|
4072
|
+
*
|
|
4073
|
+
* Recorded and matched at consume time so a registration challenge cannot be
|
|
4074
|
+
* presented to `login/verify` or the other way round — the two ceremonies
|
|
4075
|
+
* authorize very different things, and only the kind tells them apart once the
|
|
4076
|
+
* value itself is just 32 random bytes.
|
|
4077
|
+
*/
|
|
4078
|
+
declare const WEBAUTHN_CHALLENGE_KINDS: readonly ["registration", "authentication"];
|
|
4079
|
+
type WebAuthnChallengeKind = typeof WEBAUTHN_CHALLENGE_KINDS[number];
|
|
4080
|
+
declare const webauthnChallenges: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
4081
|
+
name: "webauthn_challenges";
|
|
4082
|
+
schema: string;
|
|
4083
|
+
columns: {
|
|
4084
|
+
id: drizzle_orm_pg_core.PgBuildColumn<"webauthn_challenges", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>, {
|
|
4085
|
+
name: string;
|
|
4086
|
+
tableName: "webauthn_challenges";
|
|
4087
|
+
dataType: "number int53";
|
|
4088
|
+
data: number;
|
|
4089
|
+
driverParam: number;
|
|
4090
|
+
notNull: true;
|
|
4091
|
+
hasDefault: true;
|
|
4092
|
+
isPrimaryKey: false;
|
|
4093
|
+
isAutoincrement: false;
|
|
4094
|
+
hasRuntimeDefault: false;
|
|
4095
|
+
enumValues: undefined;
|
|
4096
|
+
identity: undefined;
|
|
4097
|
+
generated: undefined;
|
|
4098
|
+
}>;
|
|
4099
|
+
challengeHash: drizzle_orm_pg_core.PgBuildColumn<"webauthn_challenges", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
4100
|
+
name: string;
|
|
4101
|
+
tableName: "webauthn_challenges";
|
|
4102
|
+
dataType: "string";
|
|
4103
|
+
data: string;
|
|
4104
|
+
driverParam: string;
|
|
4105
|
+
notNull: true;
|
|
4106
|
+
hasDefault: false;
|
|
4107
|
+
isPrimaryKey: false;
|
|
4108
|
+
isAutoincrement: false;
|
|
4109
|
+
hasRuntimeDefault: false;
|
|
4110
|
+
enumValues: undefined;
|
|
4111
|
+
identity: undefined;
|
|
4112
|
+
generated: undefined;
|
|
4113
|
+
}>;
|
|
4114
|
+
kind: drizzle_orm_pg_core.PgBuildColumn<"webauthn_challenges", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<["registration", "authentication"] & [string, ...string[]]>>, {
|
|
4115
|
+
name: string;
|
|
4116
|
+
tableName: "webauthn_challenges";
|
|
4117
|
+
dataType: "string enum";
|
|
4118
|
+
data: "registration" | "authentication";
|
|
4119
|
+
driverParam: string;
|
|
4120
|
+
notNull: true;
|
|
4121
|
+
hasDefault: false;
|
|
4122
|
+
isPrimaryKey: false;
|
|
4123
|
+
isAutoincrement: false;
|
|
4124
|
+
hasRuntimeDefault: false;
|
|
4125
|
+
enumValues: ["registration", "authentication"] & [string, ...string[]];
|
|
4126
|
+
identity: undefined;
|
|
4127
|
+
generated: undefined;
|
|
4128
|
+
}>;
|
|
4129
|
+
userId: drizzle_orm_pg_core.PgBuildColumn<"webauthn_challenges", drizzle_orm_pg_core.PgBigInt53Builder, {
|
|
4130
|
+
name: string;
|
|
4131
|
+
tableName: "webauthn_challenges";
|
|
4132
|
+
dataType: "number int53";
|
|
4133
|
+
data: number;
|
|
4134
|
+
driverParam: string | number;
|
|
4135
|
+
notNull: false;
|
|
4136
|
+
hasDefault: false;
|
|
4137
|
+
isPrimaryKey: false;
|
|
4138
|
+
isAutoincrement: false;
|
|
4139
|
+
hasRuntimeDefault: false;
|
|
4140
|
+
enumValues: undefined;
|
|
4141
|
+
identity: undefined;
|
|
4142
|
+
generated: undefined;
|
|
4143
|
+
}>;
|
|
4144
|
+
expiresAt: drizzle_orm_pg_core.PgBuildColumn<"webauthn_challenges", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTimestampBuilder>, {
|
|
4145
|
+
name: string;
|
|
4146
|
+
tableName: "webauthn_challenges";
|
|
4147
|
+
dataType: "object date";
|
|
4148
|
+
data: Date;
|
|
4149
|
+
driverParam: string;
|
|
4150
|
+
notNull: true;
|
|
4151
|
+
hasDefault: false;
|
|
4152
|
+
isPrimaryKey: false;
|
|
4153
|
+
isAutoincrement: false;
|
|
4154
|
+
hasRuntimeDefault: false;
|
|
4155
|
+
enumValues: undefined;
|
|
4156
|
+
identity: undefined;
|
|
4157
|
+
generated: undefined;
|
|
4158
|
+
}>;
|
|
4159
|
+
consumedAt: drizzle_orm_pg_core.PgBuildColumn<"webauthn_challenges", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
4160
|
+
name: string;
|
|
4161
|
+
tableName: "webauthn_challenges";
|
|
4162
|
+
dataType: "object date";
|
|
4163
|
+
data: Date;
|
|
4164
|
+
driverParam: string;
|
|
4165
|
+
notNull: false;
|
|
4166
|
+
hasDefault: false;
|
|
4167
|
+
isPrimaryKey: false;
|
|
4168
|
+
isAutoincrement: false;
|
|
4169
|
+
hasRuntimeDefault: false;
|
|
4170
|
+
enumValues: undefined;
|
|
4171
|
+
identity: undefined;
|
|
4172
|
+
generated: undefined;
|
|
4173
|
+
}>;
|
|
4174
|
+
createdAt: drizzle_orm_pg_core.PgBuildColumn<"webauthn_challenges", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTimestampBuilder>>, {
|
|
4175
|
+
name: string;
|
|
4176
|
+
tableName: "webauthn_challenges";
|
|
4177
|
+
dataType: "object date";
|
|
4178
|
+
data: Date;
|
|
4179
|
+
driverParam: string;
|
|
4180
|
+
notNull: true;
|
|
4181
|
+
hasDefault: true;
|
|
4182
|
+
isPrimaryKey: false;
|
|
4183
|
+
isAutoincrement: false;
|
|
4184
|
+
hasRuntimeDefault: false;
|
|
4185
|
+
enumValues: undefined;
|
|
4186
|
+
identity: undefined;
|
|
4187
|
+
generated: undefined;
|
|
4188
|
+
}>;
|
|
4189
|
+
};
|
|
4190
|
+
dialect: "pg";
|
|
4191
|
+
}>;
|
|
4192
|
+
type WebAuthnChallenge = typeof webauthnChallenges.$inferSelect;
|
|
4193
|
+
type NewWebAuthnChallenge = typeof webauthnChallenges.$inferInsert;
|
|
4194
|
+
|
|
4195
|
+
/**
|
|
4196
|
+
* @spfn/auth - Device Authorizations Entity
|
|
4197
|
+
*
|
|
4198
|
+
* Backs device-code login: a device with no key on file yet parks its public key
|
|
4199
|
+
* here, shows a short code, and an already-authenticated device approves it. The
|
|
4200
|
+
* parked key is registered against the approving user's account when the waiting
|
|
4201
|
+
* device polls.
|
|
4202
|
+
*
|
|
4203
|
+
* The pending key material duplicates the `user_public_keys` columns rather than
|
|
4204
|
+
* writing a row there early. A key in `user_public_keys` can sign requests, and
|
|
4205
|
+
* nothing about a row here has been approved yet — one table means "registered"
|
|
4206
|
+
* and the other means "asked to be", and neither is a state of the other.
|
|
4207
|
+
*
|
|
4208
|
+
* Nothing sweeps this table. Rows are judged by `expiresAt` on read, so a stale
|
|
4209
|
+
* row cannot authorize anything; it only keeps its user code out of circulation,
|
|
4210
|
+
* and 31^8 codes do not run out.
|
|
4211
|
+
*/
|
|
4212
|
+
/**
|
|
4213
|
+
* Lifecycle of one device-code request.
|
|
4214
|
+
*
|
|
4215
|
+
* Every transition is one-way and the two terminal states differ: `denied` is a
|
|
4216
|
+
* refusal the waiting device is told about, `consumed` is a completed login that
|
|
4217
|
+
* answers as if it never existed. `pending -> approved -> consumed` is the only
|
|
4218
|
+
* path to a registered key.
|
|
4219
|
+
*
|
|
4220
|
+
* `denied` is also where a global revocation puts a record — a revoke-all, a
|
|
4221
|
+
* password change, a deletion request — including one already approved. The
|
|
4222
|
+
* account has withdrawn it, and what the waiting device is owed for that is the
|
|
4223
|
+
* answer a refusal gives.
|
|
4224
|
+
*/
|
|
4225
|
+
declare const DEVICE_AUTH_STATUSES: readonly ["pending", "approved", "denied", "consumed"];
|
|
4226
|
+
type DeviceAuthStatus = typeof DEVICE_AUTH_STATUSES[number];
|
|
4227
|
+
declare const deviceAuthorizations: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
4228
|
+
name: "device_authorizations";
|
|
4229
|
+
schema: string;
|
|
4230
|
+
columns: {
|
|
4231
|
+
createdAt: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>, {
|
|
4232
|
+
name: string;
|
|
4233
|
+
tableName: "device_authorizations";
|
|
4234
|
+
dataType: "object date";
|
|
4235
|
+
data: Date;
|
|
4236
|
+
driverParam: string;
|
|
4237
|
+
notNull: true;
|
|
4238
|
+
hasDefault: true;
|
|
4239
|
+
isPrimaryKey: false;
|
|
4240
|
+
isAutoincrement: false;
|
|
4241
|
+
hasRuntimeDefault: false;
|
|
4242
|
+
enumValues: undefined;
|
|
4243
|
+
identity: undefined;
|
|
4244
|
+
generated: undefined;
|
|
4245
|
+
}>;
|
|
4246
|
+
updatedAt: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>>, {
|
|
4247
|
+
name: string;
|
|
4248
|
+
tableName: "device_authorizations";
|
|
4249
|
+
dataType: "object date";
|
|
4250
|
+
data: Date;
|
|
4251
|
+
driverParam: string;
|
|
4252
|
+
notNull: true;
|
|
4253
|
+
hasDefault: true;
|
|
4254
|
+
isPrimaryKey: false;
|
|
4255
|
+
isAutoincrement: false;
|
|
4256
|
+
hasRuntimeDefault: false;
|
|
4257
|
+
enumValues: undefined;
|
|
4258
|
+
identity: undefined;
|
|
4259
|
+
generated: undefined;
|
|
4260
|
+
}>;
|
|
4261
|
+
id: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>, {
|
|
4262
|
+
name: string;
|
|
4263
|
+
tableName: "device_authorizations";
|
|
4264
|
+
dataType: "number int53";
|
|
4265
|
+
data: number;
|
|
4266
|
+
driverParam: number;
|
|
4267
|
+
notNull: true;
|
|
4268
|
+
hasDefault: true;
|
|
4269
|
+
isPrimaryKey: false;
|
|
4270
|
+
isAutoincrement: false;
|
|
4271
|
+
hasRuntimeDefault: false;
|
|
4272
|
+
enumValues: undefined;
|
|
4273
|
+
identity: undefined;
|
|
4274
|
+
generated: undefined;
|
|
4275
|
+
}>;
|
|
4276
|
+
deviceCodeHash: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
4277
|
+
name: string;
|
|
4278
|
+
tableName: "device_authorizations";
|
|
4279
|
+
dataType: "string";
|
|
4280
|
+
data: string;
|
|
4281
|
+
driverParam: string;
|
|
4282
|
+
notNull: true;
|
|
4283
|
+
hasDefault: false;
|
|
4284
|
+
isPrimaryKey: false;
|
|
4285
|
+
isAutoincrement: false;
|
|
4286
|
+
hasRuntimeDefault: false;
|
|
4287
|
+
enumValues: undefined;
|
|
4288
|
+
identity: undefined;
|
|
4289
|
+
generated: undefined;
|
|
4290
|
+
}>;
|
|
4291
|
+
userCode: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
4292
|
+
name: string;
|
|
4293
|
+
tableName: "device_authorizations";
|
|
4294
|
+
dataType: "string";
|
|
4295
|
+
data: string;
|
|
4296
|
+
driverParam: string;
|
|
4297
|
+
notNull: true;
|
|
4298
|
+
hasDefault: false;
|
|
4299
|
+
isPrimaryKey: false;
|
|
4300
|
+
isAutoincrement: false;
|
|
4301
|
+
hasRuntimeDefault: false;
|
|
4302
|
+
enumValues: undefined;
|
|
4303
|
+
identity: undefined;
|
|
4304
|
+
generated: undefined;
|
|
4305
|
+
}>;
|
|
4306
|
+
publicKey: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
4307
|
+
name: string;
|
|
4308
|
+
tableName: "device_authorizations";
|
|
4309
|
+
dataType: "string";
|
|
4310
|
+
data: string;
|
|
4311
|
+
driverParam: string;
|
|
4312
|
+
notNull: true;
|
|
4313
|
+
hasDefault: false;
|
|
4314
|
+
isPrimaryKey: false;
|
|
4315
|
+
isAutoincrement: false;
|
|
4316
|
+
hasRuntimeDefault: false;
|
|
4317
|
+
enumValues: undefined;
|
|
4318
|
+
identity: undefined;
|
|
4319
|
+
generated: undefined;
|
|
4320
|
+
}>;
|
|
4321
|
+
keyId: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
4322
|
+
name: string;
|
|
4323
|
+
tableName: "device_authorizations";
|
|
4324
|
+
dataType: "string";
|
|
4325
|
+
data: string;
|
|
4326
|
+
driverParam: string;
|
|
4327
|
+
notNull: true;
|
|
4328
|
+
hasDefault: false;
|
|
4329
|
+
isPrimaryKey: false;
|
|
4330
|
+
isAutoincrement: false;
|
|
4331
|
+
hasRuntimeDefault: false;
|
|
4332
|
+
enumValues: undefined;
|
|
4333
|
+
identity: undefined;
|
|
4334
|
+
generated: undefined;
|
|
4335
|
+
}>;
|
|
4336
|
+
fingerprint: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
4337
|
+
name: string;
|
|
4338
|
+
tableName: "device_authorizations";
|
|
4339
|
+
dataType: "string";
|
|
4340
|
+
data: string;
|
|
4341
|
+
driverParam: string;
|
|
4342
|
+
notNull: true;
|
|
4343
|
+
hasDefault: false;
|
|
4344
|
+
isPrimaryKey: false;
|
|
4345
|
+
isAutoincrement: false;
|
|
4346
|
+
hasRuntimeDefault: false;
|
|
4347
|
+
enumValues: undefined;
|
|
4348
|
+
identity: undefined;
|
|
4349
|
+
generated: undefined;
|
|
4350
|
+
}>;
|
|
4351
|
+
algorithm: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<["ES256", "RS256"] & [string, ...string[]]>>>, {
|
|
4352
|
+
name: string;
|
|
4353
|
+
tableName: "device_authorizations";
|
|
4354
|
+
dataType: "string enum";
|
|
4355
|
+
data: "ES256" | "RS256";
|
|
4356
|
+
driverParam: string;
|
|
4357
|
+
notNull: true;
|
|
4358
|
+
hasDefault: true;
|
|
4359
|
+
isPrimaryKey: false;
|
|
4360
|
+
isAutoincrement: false;
|
|
4361
|
+
hasRuntimeDefault: false;
|
|
4362
|
+
enumValues: ["ES256", "RS256"] & [string, ...string[]];
|
|
4363
|
+
identity: undefined;
|
|
4364
|
+
generated: undefined;
|
|
4365
|
+
}>;
|
|
4366
|
+
deviceName: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
|
|
4367
|
+
name: string;
|
|
4368
|
+
tableName: "device_authorizations";
|
|
4369
|
+
dataType: "string";
|
|
4370
|
+
data: string;
|
|
4371
|
+
driverParam: string;
|
|
4372
|
+
notNull: false;
|
|
4373
|
+
hasDefault: false;
|
|
4374
|
+
isPrimaryKey: false;
|
|
4375
|
+
isAutoincrement: false;
|
|
4376
|
+
hasRuntimeDefault: false;
|
|
4377
|
+
enumValues: undefined;
|
|
4378
|
+
identity: undefined;
|
|
4379
|
+
generated: undefined;
|
|
4380
|
+
}>;
|
|
4381
|
+
platform: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.PgTextBuilder<["ios", "android", "web", "desktop"] & [string, ...string[]]>, {
|
|
4382
|
+
name: string;
|
|
4383
|
+
tableName: "device_authorizations";
|
|
4384
|
+
dataType: "string enum";
|
|
4385
|
+
data: "ios" | "android" | "web" | "desktop";
|
|
4386
|
+
driverParam: string;
|
|
4387
|
+
notNull: false;
|
|
4388
|
+
hasDefault: false;
|
|
4389
|
+
isPrimaryKey: false;
|
|
4390
|
+
isAutoincrement: false;
|
|
4391
|
+
hasRuntimeDefault: false;
|
|
4392
|
+
enumValues: ["ios", "android", "web", "desktop"] & [string, ...string[]];
|
|
4393
|
+
identity: undefined;
|
|
4394
|
+
generated: undefined;
|
|
4395
|
+
}>;
|
|
4396
|
+
status: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<["pending", "approved", "denied", "consumed"] & [string, ...string[]]>>>, {
|
|
4397
|
+
name: string;
|
|
4398
|
+
tableName: "device_authorizations";
|
|
4399
|
+
dataType: "string enum";
|
|
4400
|
+
data: "pending" | "approved" | "denied" | "consumed";
|
|
4401
|
+
driverParam: string;
|
|
4402
|
+
notNull: true;
|
|
4403
|
+
hasDefault: true;
|
|
4404
|
+
isPrimaryKey: false;
|
|
4405
|
+
isAutoincrement: false;
|
|
4406
|
+
hasRuntimeDefault: false;
|
|
4407
|
+
enumValues: ["pending", "approved", "denied", "consumed"] & [string, ...string[]];
|
|
4408
|
+
identity: undefined;
|
|
4409
|
+
generated: undefined;
|
|
4410
|
+
}>;
|
|
4411
|
+
userId: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.PgBigInt53Builder, {
|
|
4412
|
+
name: string;
|
|
4413
|
+
tableName: "device_authorizations";
|
|
4414
|
+
dataType: "number int53";
|
|
4415
|
+
data: number;
|
|
4416
|
+
driverParam: string | number;
|
|
4417
|
+
notNull: false;
|
|
4418
|
+
hasDefault: false;
|
|
4419
|
+
isPrimaryKey: false;
|
|
4420
|
+
isAutoincrement: false;
|
|
4421
|
+
hasRuntimeDefault: false;
|
|
4422
|
+
enumValues: undefined;
|
|
4423
|
+
identity: undefined;
|
|
4424
|
+
generated: undefined;
|
|
4425
|
+
}>;
|
|
4426
|
+
expiresAt: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTimestampBuilder>, {
|
|
4427
|
+
name: string;
|
|
4428
|
+
tableName: "device_authorizations";
|
|
4429
|
+
dataType: "object date";
|
|
4430
|
+
data: Date;
|
|
4431
|
+
driverParam: string;
|
|
4432
|
+
notNull: true;
|
|
4433
|
+
hasDefault: false;
|
|
4434
|
+
isPrimaryKey: false;
|
|
4435
|
+
isAutoincrement: false;
|
|
4436
|
+
hasRuntimeDefault: false;
|
|
4437
|
+
enumValues: undefined;
|
|
4438
|
+
identity: undefined;
|
|
4439
|
+
generated: undefined;
|
|
4440
|
+
}>;
|
|
4441
|
+
approvedAt: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
4442
|
+
name: string;
|
|
4443
|
+
tableName: "device_authorizations";
|
|
4444
|
+
dataType: "object date";
|
|
4445
|
+
data: Date;
|
|
4446
|
+
driverParam: string;
|
|
4447
|
+
notNull: false;
|
|
4448
|
+
hasDefault: false;
|
|
4449
|
+
isPrimaryKey: false;
|
|
4450
|
+
isAutoincrement: false;
|
|
4451
|
+
hasRuntimeDefault: false;
|
|
4452
|
+
enumValues: undefined;
|
|
4453
|
+
identity: undefined;
|
|
4454
|
+
generated: undefined;
|
|
4455
|
+
}>;
|
|
4456
|
+
consumedAt: drizzle_orm_pg_core.PgBuildColumn<"device_authorizations", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
4457
|
+
name: string;
|
|
4458
|
+
tableName: "device_authorizations";
|
|
4459
|
+
dataType: "object date";
|
|
4460
|
+
data: Date;
|
|
4461
|
+
driverParam: string;
|
|
4462
|
+
notNull: false;
|
|
4463
|
+
hasDefault: false;
|
|
4464
|
+
isPrimaryKey: false;
|
|
4465
|
+
isAutoincrement: false;
|
|
4466
|
+
hasRuntimeDefault: false;
|
|
4467
|
+
enumValues: undefined;
|
|
4468
|
+
identity: undefined;
|
|
4469
|
+
generated: undefined;
|
|
4470
|
+
}>;
|
|
4471
|
+
};
|
|
4472
|
+
dialect: "pg";
|
|
4473
|
+
}>;
|
|
4474
|
+
type DeviceAuthorization = typeof deviceAuthorizations.$inferSelect;
|
|
4475
|
+
type NewDeviceAuthorization = typeof deviceAuthorizations.$inferInsert;
|
|
4476
|
+
|
|
4477
|
+
/**
|
|
4478
|
+
* @spfn/auth - Permissions Entity
|
|
4479
|
+
*
|
|
4480
|
+
* Granular permissions for RBAC system
|
|
4481
|
+
*
|
|
4482
|
+
* Features:
|
|
4483
|
+
* - Built-in permissions (auth:*, user:*, rbac:*) - required for package
|
|
4484
|
+
* - System permissions (preset permissions) - optional
|
|
4485
|
+
* - Custom permissions (app-specific) - defined by developers
|
|
4486
|
+
* - Category grouping for organization
|
|
4487
|
+
*/
|
|
4488
|
+
declare const permissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
4489
|
+
name: "permissions";
|
|
4490
|
+
schema: string;
|
|
4491
|
+
columns: {
|
|
4492
|
+
createdAt: drizzle_orm_pg_core.PgBuildColumn<"permissions", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>, {
|
|
4493
|
+
name: string;
|
|
4494
|
+
tableName: "permissions";
|
|
4495
|
+
dataType: "object date";
|
|
4496
|
+
data: Date;
|
|
4497
|
+
driverParam: string;
|
|
4498
|
+
notNull: true;
|
|
4499
|
+
hasDefault: true;
|
|
4500
|
+
isPrimaryKey: false;
|
|
4501
|
+
isAutoincrement: false;
|
|
4502
|
+
hasRuntimeDefault: false;
|
|
4503
|
+
enumValues: undefined;
|
|
4504
|
+
identity: undefined;
|
|
4505
|
+
generated: undefined;
|
|
4506
|
+
}>;
|
|
4507
|
+
updatedAt: drizzle_orm_pg_core.PgBuildColumn<"permissions", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>>, {
|
|
4508
|
+
name: string;
|
|
4509
|
+
tableName: "permissions";
|
|
4510
|
+
dataType: "object date";
|
|
4511
|
+
data: Date;
|
|
4512
|
+
driverParam: string;
|
|
4513
|
+
notNull: true;
|
|
4514
|
+
hasDefault: true;
|
|
4515
|
+
isPrimaryKey: false;
|
|
4516
|
+
isAutoincrement: false;
|
|
4517
|
+
hasRuntimeDefault: false;
|
|
4518
|
+
enumValues: undefined;
|
|
4519
|
+
identity: undefined;
|
|
4520
|
+
generated: undefined;
|
|
4521
|
+
}>;
|
|
4522
|
+
id: drizzle_orm_pg_core.PgBuildColumn<"permissions", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>, {
|
|
4523
|
+
name: string;
|
|
4524
|
+
tableName: "permissions";
|
|
4525
|
+
dataType: "number int53";
|
|
4526
|
+
data: number;
|
|
4527
|
+
driverParam: number;
|
|
4528
|
+
notNull: true;
|
|
4529
|
+
hasDefault: true;
|
|
4530
|
+
isPrimaryKey: false;
|
|
4531
|
+
isAutoincrement: false;
|
|
4532
|
+
hasRuntimeDefault: false;
|
|
4533
|
+
enumValues: undefined;
|
|
4534
|
+
identity: undefined;
|
|
4535
|
+
generated: undefined;
|
|
4536
|
+
}>;
|
|
4537
|
+
name: drizzle_orm_pg_core.PgBuildColumn<"permissions", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
4538
|
+
name: string;
|
|
4539
|
+
tableName: "permissions";
|
|
4540
|
+
dataType: "string";
|
|
4541
|
+
data: string;
|
|
4542
|
+
driverParam: string;
|
|
4543
|
+
notNull: true;
|
|
4544
|
+
hasDefault: false;
|
|
4545
|
+
isPrimaryKey: false;
|
|
4546
|
+
isAutoincrement: false;
|
|
4547
|
+
hasRuntimeDefault: false;
|
|
4548
|
+
enumValues: undefined;
|
|
4549
|
+
identity: undefined;
|
|
4550
|
+
generated: undefined;
|
|
4551
|
+
}>;
|
|
4552
|
+
displayName: drizzle_orm_pg_core.PgBuildColumn<"permissions", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
4553
|
+
name: string;
|
|
4554
|
+
tableName: "permissions";
|
|
4555
|
+
dataType: "string";
|
|
4556
|
+
data: string;
|
|
4557
|
+
driverParam: string;
|
|
4558
|
+
notNull: true;
|
|
4559
|
+
hasDefault: false;
|
|
4560
|
+
isPrimaryKey: false;
|
|
4561
|
+
isAutoincrement: false;
|
|
4562
|
+
hasRuntimeDefault: false;
|
|
4563
|
+
enumValues: undefined;
|
|
4564
|
+
identity: undefined;
|
|
4565
|
+
generated: undefined;
|
|
4566
|
+
}>;
|
|
4567
|
+
description: drizzle_orm_pg_core.PgBuildColumn<"permissions", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
|
|
4568
|
+
name: string;
|
|
4569
|
+
tableName: "permissions";
|
|
4570
|
+
dataType: "string";
|
|
4571
|
+
data: string;
|
|
4572
|
+
driverParam: string;
|
|
4573
|
+
notNull: false;
|
|
4574
|
+
hasDefault: false;
|
|
4575
|
+
isPrimaryKey: false;
|
|
4576
|
+
isAutoincrement: false;
|
|
4577
|
+
hasRuntimeDefault: false;
|
|
4578
|
+
enumValues: undefined;
|
|
4579
|
+
identity: undefined;
|
|
4580
|
+
generated: undefined;
|
|
4581
|
+
}>;
|
|
4582
|
+
category: drizzle_orm_pg_core.PgBuildColumn<"permissions", drizzle_orm_pg_core.PgTextBuilder<["auth", "user", "rbac", "system", "custom"] & [string, ...string[]]>, {
|
|
4583
|
+
name: string;
|
|
4584
|
+
tableName: "permissions";
|
|
4585
|
+
dataType: "string enum";
|
|
4586
|
+
data: "auth" | "custom" | "user" | "rbac" | "system";
|
|
4587
|
+
driverParam: string;
|
|
4588
|
+
notNull: false;
|
|
4589
|
+
hasDefault: false;
|
|
4590
|
+
isPrimaryKey: false;
|
|
4591
|
+
isAutoincrement: false;
|
|
4592
|
+
hasRuntimeDefault: false;
|
|
4593
|
+
enumValues: ["auth", "user", "rbac", "system", "custom"] & [string, ...string[]];
|
|
4594
|
+
identity: undefined;
|
|
4595
|
+
generated: undefined;
|
|
4596
|
+
}>;
|
|
4597
|
+
isBuiltin: drizzle_orm_pg_core.PgBuildColumn<"permissions", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBooleanBuilder>>, {
|
|
4598
|
+
name: string;
|
|
4599
|
+
tableName: "permissions";
|
|
4600
|
+
dataType: "boolean";
|
|
4601
|
+
data: boolean;
|
|
4602
|
+
driverParam: boolean;
|
|
4603
|
+
notNull: true;
|
|
4604
|
+
hasDefault: true;
|
|
4605
|
+
isPrimaryKey: false;
|
|
4606
|
+
isAutoincrement: false;
|
|
4607
|
+
hasRuntimeDefault: false;
|
|
4608
|
+
enumValues: undefined;
|
|
4609
|
+
identity: undefined;
|
|
4610
|
+
generated: undefined;
|
|
4611
|
+
}>;
|
|
4612
|
+
isSystem: drizzle_orm_pg_core.PgBuildColumn<"permissions", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBooleanBuilder>>, {
|
|
4613
|
+
name: string;
|
|
4614
|
+
tableName: "permissions";
|
|
4615
|
+
dataType: "boolean";
|
|
4616
|
+
data: boolean;
|
|
4617
|
+
driverParam: boolean;
|
|
4618
|
+
notNull: true;
|
|
4619
|
+
hasDefault: true;
|
|
4620
|
+
isPrimaryKey: false;
|
|
4621
|
+
isAutoincrement: false;
|
|
4622
|
+
hasRuntimeDefault: false;
|
|
4623
|
+
enumValues: undefined;
|
|
4624
|
+
identity: undefined;
|
|
4625
|
+
generated: undefined;
|
|
4626
|
+
}>;
|
|
4627
|
+
isActive: drizzle_orm_pg_core.PgBuildColumn<"permissions", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBooleanBuilder>>, {
|
|
4628
|
+
name: string;
|
|
4629
|
+
tableName: "permissions";
|
|
4630
|
+
dataType: "boolean";
|
|
4631
|
+
data: boolean;
|
|
4632
|
+
driverParam: boolean;
|
|
4633
|
+
notNull: true;
|
|
4634
|
+
hasDefault: true;
|
|
4635
|
+
isPrimaryKey: false;
|
|
4636
|
+
isAutoincrement: false;
|
|
4637
|
+
hasRuntimeDefault: false;
|
|
4638
|
+
enumValues: undefined;
|
|
4639
|
+
identity: undefined;
|
|
4640
|
+
generated: undefined;
|
|
4641
|
+
}>;
|
|
4642
|
+
metadata: drizzle_orm_pg_core.PgBuildColumn<"permissions", drizzle_orm_pg_core.Set$Type<drizzle_orm_pg_core.PgJsonbBuilder, Record<string, any>>, {
|
|
4643
|
+
name: string;
|
|
4644
|
+
tableName: "permissions";
|
|
4645
|
+
dataType: "object json";
|
|
4646
|
+
data: Record<string, any>;
|
|
4647
|
+
driverParam: unknown;
|
|
4648
|
+
notNull: false;
|
|
4649
|
+
hasDefault: false;
|
|
4650
|
+
isPrimaryKey: false;
|
|
4651
|
+
isAutoincrement: false;
|
|
4652
|
+
hasRuntimeDefault: false;
|
|
4653
|
+
enumValues: undefined;
|
|
4654
|
+
identity: undefined;
|
|
4655
|
+
generated: undefined;
|
|
4656
|
+
}>;
|
|
4657
|
+
};
|
|
4658
|
+
dialect: "pg";
|
|
4659
|
+
}>;
|
|
4660
|
+
type PermissionEntity = typeof permissions.$inferSelect;
|
|
4661
|
+
type NewPermissionEntity = typeof permissions.$inferInsert;
|
|
4662
|
+
type Permission = PermissionEntity;
|
|
4663
|
+
type NewPermission = NewPermissionEntity;
|
|
4664
|
+
|
|
4665
|
+
/**
|
|
4666
|
+
* @spfn/auth - Role-Permissions Mapping Entity
|
|
4667
|
+
*
|
|
4668
|
+
* Many-to-many relationship between roles and permissions
|
|
4669
|
+
*
|
|
4670
|
+
* Usage:
|
|
4671
|
+
* - Defines which permissions each role has
|
|
4672
|
+
* - Cascade delete when role or permission is deleted
|
|
4673
|
+
*/
|
|
4674
|
+
declare const rolePermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
4675
|
+
name: "role_permissions";
|
|
4676
|
+
schema: string;
|
|
4677
|
+
columns: {
|
|
4678
|
+
createdAt: drizzle_orm_pg_core.PgBuildColumn<"role_permissions", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>, {
|
|
4679
|
+
name: string;
|
|
4680
|
+
tableName: "role_permissions";
|
|
4681
|
+
dataType: "object date";
|
|
4682
|
+
data: Date;
|
|
4683
|
+
driverParam: string;
|
|
4684
|
+
notNull: true;
|
|
4685
|
+
hasDefault: true;
|
|
4686
|
+
isPrimaryKey: false;
|
|
4687
|
+
isAutoincrement: false;
|
|
4688
|
+
hasRuntimeDefault: false;
|
|
4689
|
+
enumValues: undefined;
|
|
4690
|
+
identity: undefined;
|
|
4691
|
+
generated: undefined;
|
|
4692
|
+
}>;
|
|
4693
|
+
updatedAt: drizzle_orm_pg_core.PgBuildColumn<"role_permissions", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>>, {
|
|
4694
|
+
name: string;
|
|
4695
|
+
tableName: "role_permissions";
|
|
4696
|
+
dataType: "object date";
|
|
4697
|
+
data: Date;
|
|
4698
|
+
driverParam: string;
|
|
4699
|
+
notNull: true;
|
|
4700
|
+
hasDefault: true;
|
|
4701
|
+
isPrimaryKey: false;
|
|
4702
|
+
isAutoincrement: false;
|
|
4703
|
+
hasRuntimeDefault: false;
|
|
4704
|
+
enumValues: undefined;
|
|
4705
|
+
identity: undefined;
|
|
4706
|
+
generated: undefined;
|
|
4707
|
+
}>;
|
|
4708
|
+
id: drizzle_orm_pg_core.PgBuildColumn<"role_permissions", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>, {
|
|
4709
|
+
name: string;
|
|
4710
|
+
tableName: "role_permissions";
|
|
4711
|
+
dataType: "number int53";
|
|
4712
|
+
data: number;
|
|
4713
|
+
driverParam: number;
|
|
4714
|
+
notNull: true;
|
|
4715
|
+
hasDefault: true;
|
|
4716
|
+
isPrimaryKey: false;
|
|
4717
|
+
isAutoincrement: false;
|
|
4718
|
+
hasRuntimeDefault: false;
|
|
4719
|
+
enumValues: undefined;
|
|
4720
|
+
identity: undefined;
|
|
4721
|
+
generated: undefined;
|
|
4722
|
+
}>;
|
|
4723
|
+
roleId: drizzle_orm_pg_core.PgBuildColumn<"role_permissions", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>, {
|
|
4724
|
+
name: string;
|
|
4725
|
+
tableName: "role_permissions";
|
|
4726
|
+
dataType: "number int53";
|
|
4727
|
+
data: number;
|
|
4728
|
+
driverParam: string | number;
|
|
4729
|
+
notNull: true;
|
|
4730
|
+
hasDefault: false;
|
|
4731
|
+
isPrimaryKey: false;
|
|
4732
|
+
isAutoincrement: false;
|
|
4733
|
+
hasRuntimeDefault: false;
|
|
4734
|
+
enumValues: undefined;
|
|
4735
|
+
identity: undefined;
|
|
4736
|
+
generated: undefined;
|
|
4737
|
+
}>;
|
|
4738
|
+
permissionId: drizzle_orm_pg_core.PgBuildColumn<"role_permissions", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>, {
|
|
4739
|
+
name: string;
|
|
4740
|
+
tableName: "role_permissions";
|
|
4741
|
+
dataType: "number int53";
|
|
4742
|
+
data: number;
|
|
4743
|
+
driverParam: string | number;
|
|
4744
|
+
notNull: true;
|
|
4745
|
+
hasDefault: false;
|
|
4746
|
+
isPrimaryKey: false;
|
|
4747
|
+
isAutoincrement: false;
|
|
4748
|
+
hasRuntimeDefault: false;
|
|
4749
|
+
enumValues: undefined;
|
|
4750
|
+
identity: undefined;
|
|
4751
|
+
generated: undefined;
|
|
4752
|
+
}>;
|
|
4753
|
+
};
|
|
4754
|
+
dialect: "pg";
|
|
4755
|
+
}>;
|
|
4756
|
+
type RolePermission = typeof rolePermissions.$inferSelect;
|
|
4757
|
+
type NewRolePermission = typeof rolePermissions.$inferInsert;
|
|
4758
|
+
|
|
4759
|
+
/**
|
|
4760
|
+
* @spfn/auth - User-Permissions Override Entity
|
|
4761
|
+
*
|
|
4762
|
+
* Per-user permission grants/revocations
|
|
4763
|
+
*
|
|
4764
|
+
* Features:
|
|
4765
|
+
* - Grant additional permissions to specific users
|
|
4766
|
+
* - Revoke role-inherited permissions from specific users
|
|
4767
|
+
* - Temporary permissions with expiration
|
|
4768
|
+
* - Audit trail with reason field
|
|
4769
|
+
*
|
|
4770
|
+
* Priority:
|
|
4771
|
+
* User permissions override role permissions
|
|
4772
|
+
*/
|
|
4773
|
+
declare const userPermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
4774
|
+
name: "user_permissions";
|
|
4775
|
+
schema: string;
|
|
4776
|
+
columns: {
|
|
4777
|
+
createdAt: drizzle_orm_pg_core.PgBuildColumn<"user_permissions", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>, {
|
|
4778
|
+
name: string;
|
|
4779
|
+
tableName: "user_permissions";
|
|
4780
|
+
dataType: "object date";
|
|
4781
|
+
data: Date;
|
|
4782
|
+
driverParam: string;
|
|
4783
|
+
notNull: true;
|
|
4784
|
+
hasDefault: true;
|
|
4785
|
+
isPrimaryKey: false;
|
|
4786
|
+
isAutoincrement: false;
|
|
4787
|
+
hasRuntimeDefault: false;
|
|
4788
|
+
enumValues: undefined;
|
|
4789
|
+
identity: undefined;
|
|
4790
|
+
generated: undefined;
|
|
4791
|
+
}>;
|
|
4792
|
+
updatedAt: drizzle_orm_pg_core.PgBuildColumn<"user_permissions", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>>, {
|
|
4793
|
+
name: string;
|
|
4794
|
+
tableName: "user_permissions";
|
|
4795
|
+
dataType: "object date";
|
|
4796
|
+
data: Date;
|
|
4797
|
+
driverParam: string;
|
|
4798
|
+
notNull: true;
|
|
4799
|
+
hasDefault: true;
|
|
4800
|
+
isPrimaryKey: false;
|
|
4801
|
+
isAutoincrement: false;
|
|
4802
|
+
hasRuntimeDefault: false;
|
|
4803
|
+
enumValues: undefined;
|
|
4804
|
+
identity: undefined;
|
|
4805
|
+
generated: undefined;
|
|
4806
|
+
}>;
|
|
4807
|
+
id: drizzle_orm_pg_core.PgBuildColumn<"user_permissions", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>, {
|
|
4808
|
+
name: string;
|
|
4809
|
+
tableName: "user_permissions";
|
|
4810
|
+
dataType: "number int53";
|
|
4811
|
+
data: number;
|
|
4812
|
+
driverParam: number;
|
|
4813
|
+
notNull: true;
|
|
4814
|
+
hasDefault: true;
|
|
4815
|
+
isPrimaryKey: false;
|
|
4816
|
+
isAutoincrement: false;
|
|
4817
|
+
hasRuntimeDefault: false;
|
|
4818
|
+
enumValues: undefined;
|
|
4819
|
+
identity: undefined;
|
|
4820
|
+
generated: undefined;
|
|
4821
|
+
}>;
|
|
4822
|
+
userId: drizzle_orm_pg_core.PgBuildColumn<"user_permissions", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>, {
|
|
4823
|
+
name: string;
|
|
4824
|
+
tableName: "user_permissions";
|
|
4825
|
+
dataType: "number int53";
|
|
4826
|
+
data: number;
|
|
4827
|
+
driverParam: string | number;
|
|
4828
|
+
notNull: true;
|
|
4829
|
+
hasDefault: false;
|
|
4830
|
+
isPrimaryKey: false;
|
|
4831
|
+
isAutoincrement: false;
|
|
4832
|
+
hasRuntimeDefault: false;
|
|
4833
|
+
enumValues: undefined;
|
|
4834
|
+
identity: undefined;
|
|
4835
|
+
generated: undefined;
|
|
4836
|
+
}>;
|
|
4837
|
+
permissionId: drizzle_orm_pg_core.PgBuildColumn<"user_permissions", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>, {
|
|
4838
|
+
name: string;
|
|
4839
|
+
tableName: "user_permissions";
|
|
4840
|
+
dataType: "number int53";
|
|
4841
|
+
data: number;
|
|
4842
|
+
driverParam: string | number;
|
|
4843
|
+
notNull: true;
|
|
4844
|
+
hasDefault: false;
|
|
4845
|
+
isPrimaryKey: false;
|
|
4846
|
+
isAutoincrement: false;
|
|
4847
|
+
hasRuntimeDefault: false;
|
|
4848
|
+
enumValues: undefined;
|
|
4849
|
+
identity: undefined;
|
|
4850
|
+
generated: undefined;
|
|
4851
|
+
}>;
|
|
4852
|
+
granted: drizzle_orm_pg_core.PgBuildColumn<"user_permissions", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBooleanBuilder>>, {
|
|
4853
|
+
name: string;
|
|
4854
|
+
tableName: "user_permissions";
|
|
4855
|
+
dataType: "boolean";
|
|
4856
|
+
data: boolean;
|
|
4857
|
+
driverParam: boolean;
|
|
4858
|
+
notNull: true;
|
|
4859
|
+
hasDefault: true;
|
|
4860
|
+
isPrimaryKey: false;
|
|
4861
|
+
isAutoincrement: false;
|
|
4862
|
+
hasRuntimeDefault: false;
|
|
4863
|
+
enumValues: undefined;
|
|
4864
|
+
identity: undefined;
|
|
4865
|
+
generated: undefined;
|
|
4866
|
+
}>;
|
|
4867
|
+
reason: drizzle_orm_pg_core.PgBuildColumn<"user_permissions", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
|
|
4868
|
+
name: string;
|
|
4869
|
+
tableName: "user_permissions";
|
|
4870
|
+
dataType: "string";
|
|
4871
|
+
data: string;
|
|
4872
|
+
driverParam: string;
|
|
4873
|
+
notNull: false;
|
|
4874
|
+
hasDefault: false;
|
|
4875
|
+
isPrimaryKey: false;
|
|
4876
|
+
isAutoincrement: false;
|
|
4877
|
+
hasRuntimeDefault: false;
|
|
4878
|
+
enumValues: undefined;
|
|
4879
|
+
identity: undefined;
|
|
4880
|
+
generated: undefined;
|
|
4881
|
+
}>;
|
|
4882
|
+
expiresAt: drizzle_orm_pg_core.PgBuildColumn<"user_permissions", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
4883
|
+
name: string;
|
|
4884
|
+
tableName: "user_permissions";
|
|
3467
4885
|
dataType: "object date";
|
|
3468
4886
|
data: Date;
|
|
3469
4887
|
driverParam: string;
|
|
@@ -3482,6 +4900,666 @@ declare const userPermissions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
3482
4900
|
type UserPermission = typeof userPermissions.$inferSelect;
|
|
3483
4901
|
type NewUserPermission = typeof userPermissions.$inferInsert;
|
|
3484
4902
|
|
|
4903
|
+
/**
|
|
4904
|
+
* @spfn/auth - OAuth 2.1 Clients Entity
|
|
4905
|
+
*
|
|
4906
|
+
* One row per public client that registered itself through RFC 7591 dynamic
|
|
4907
|
+
* client registration — a `claude mcp add` on somebody's laptop, a `codex`
|
|
4908
|
+
* install, a second copy of either on the same machine. There is no secret
|
|
4909
|
+
* column because there is no secret: these clients run on the user's own
|
|
4910
|
+
* device, cannot keep one, and authenticate with `token_endpoint_auth_method:
|
|
4911
|
+
* "none"` plus PKCE.
|
|
4912
|
+
*
|
|
4913
|
+
* `oauth2_` and not `oauth_`: the `oauth` prefix in this package belongs to the
|
|
4914
|
+
* social-login client we are on the other side of (Google, Kakao). Everything
|
|
4915
|
+
* named `oauth2` here is the authorization server we run.
|
|
4916
|
+
*
|
|
4917
|
+
* Registration is unauthenticated by definition, so a row here is nothing but a
|
|
4918
|
+
* claim until a user approves it. `auth.oauth2.client-purge` deletes the rows
|
|
4919
|
+
* that never were: a client older than a day with no grant against it.
|
|
4920
|
+
*/
|
|
4921
|
+
declare const oauth2Clients: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
4922
|
+
name: "oauth2_clients";
|
|
4923
|
+
schema: string;
|
|
4924
|
+
columns: {
|
|
4925
|
+
createdAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_clients", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>, {
|
|
4926
|
+
name: string;
|
|
4927
|
+
tableName: "oauth2_clients";
|
|
4928
|
+
dataType: "object date";
|
|
4929
|
+
data: Date;
|
|
4930
|
+
driverParam: string;
|
|
4931
|
+
notNull: true;
|
|
4932
|
+
hasDefault: true;
|
|
4933
|
+
isPrimaryKey: false;
|
|
4934
|
+
isAutoincrement: false;
|
|
4935
|
+
hasRuntimeDefault: false;
|
|
4936
|
+
enumValues: undefined;
|
|
4937
|
+
identity: undefined;
|
|
4938
|
+
generated: undefined;
|
|
4939
|
+
}>;
|
|
4940
|
+
updatedAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_clients", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>>, {
|
|
4941
|
+
name: string;
|
|
4942
|
+
tableName: "oauth2_clients";
|
|
4943
|
+
dataType: "object date";
|
|
4944
|
+
data: Date;
|
|
4945
|
+
driverParam: string;
|
|
4946
|
+
notNull: true;
|
|
4947
|
+
hasDefault: true;
|
|
4948
|
+
isPrimaryKey: false;
|
|
4949
|
+
isAutoincrement: false;
|
|
4950
|
+
hasRuntimeDefault: false;
|
|
4951
|
+
enumValues: undefined;
|
|
4952
|
+
identity: undefined;
|
|
4953
|
+
generated: undefined;
|
|
4954
|
+
}>;
|
|
4955
|
+
id: drizzle_orm_pg_core.PgBuildColumn<"oauth2_clients", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>, {
|
|
4956
|
+
name: string;
|
|
4957
|
+
tableName: "oauth2_clients";
|
|
4958
|
+
dataType: "number int53";
|
|
4959
|
+
data: number;
|
|
4960
|
+
driverParam: number;
|
|
4961
|
+
notNull: true;
|
|
4962
|
+
hasDefault: true;
|
|
4963
|
+
isPrimaryKey: false;
|
|
4964
|
+
isAutoincrement: false;
|
|
4965
|
+
hasRuntimeDefault: false;
|
|
4966
|
+
enumValues: undefined;
|
|
4967
|
+
identity: undefined;
|
|
4968
|
+
generated: undefined;
|
|
4969
|
+
}>;
|
|
4970
|
+
clientId: drizzle_orm_pg_core.PgBuildColumn<"oauth2_clients", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
4971
|
+
name: string;
|
|
4972
|
+
tableName: "oauth2_clients";
|
|
4973
|
+
dataType: "string";
|
|
4974
|
+
data: string;
|
|
4975
|
+
driverParam: string;
|
|
4976
|
+
notNull: true;
|
|
4977
|
+
hasDefault: false;
|
|
4978
|
+
isPrimaryKey: false;
|
|
4979
|
+
isAutoincrement: false;
|
|
4980
|
+
hasRuntimeDefault: false;
|
|
4981
|
+
enumValues: undefined;
|
|
4982
|
+
identity: undefined;
|
|
4983
|
+
generated: undefined;
|
|
4984
|
+
}>;
|
|
4985
|
+
clientName: drizzle_orm_pg_core.PgBuildColumn<"oauth2_clients", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
4986
|
+
name: string;
|
|
4987
|
+
tableName: "oauth2_clients";
|
|
4988
|
+
dataType: "string";
|
|
4989
|
+
data: string;
|
|
4990
|
+
driverParam: string;
|
|
4991
|
+
notNull: true;
|
|
4992
|
+
hasDefault: false;
|
|
4993
|
+
isPrimaryKey: false;
|
|
4994
|
+
isAutoincrement: false;
|
|
4995
|
+
hasRuntimeDefault: false;
|
|
4996
|
+
enumValues: undefined;
|
|
4997
|
+
identity: undefined;
|
|
4998
|
+
generated: undefined;
|
|
4999
|
+
}>;
|
|
5000
|
+
redirectUris: drizzle_orm_pg_core.PgBuildColumn<"oauth2_clients", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetDimensions<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, 1>>, {
|
|
5001
|
+
name: string;
|
|
5002
|
+
tableName: "oauth2_clients";
|
|
5003
|
+
dataType: "string";
|
|
5004
|
+
data: string[];
|
|
5005
|
+
driverParam: string | string[];
|
|
5006
|
+
notNull: true;
|
|
5007
|
+
hasDefault: false;
|
|
5008
|
+
isPrimaryKey: false;
|
|
5009
|
+
isAutoincrement: false;
|
|
5010
|
+
hasRuntimeDefault: false;
|
|
5011
|
+
enumValues: undefined;
|
|
5012
|
+
identity: undefined;
|
|
5013
|
+
generated: undefined;
|
|
5014
|
+
}>;
|
|
5015
|
+
createdIp: drizzle_orm_pg_core.PgBuildColumn<"oauth2_clients", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
|
|
5016
|
+
name: string;
|
|
5017
|
+
tableName: "oauth2_clients";
|
|
5018
|
+
dataType: "string";
|
|
5019
|
+
data: string;
|
|
5020
|
+
driverParam: string;
|
|
5021
|
+
notNull: false;
|
|
5022
|
+
hasDefault: false;
|
|
5023
|
+
isPrimaryKey: false;
|
|
5024
|
+
isAutoincrement: false;
|
|
5025
|
+
hasRuntimeDefault: false;
|
|
5026
|
+
enumValues: undefined;
|
|
5027
|
+
identity: undefined;
|
|
5028
|
+
generated: undefined;
|
|
5029
|
+
}>;
|
|
5030
|
+
lastUsedAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_clients", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
5031
|
+
name: string;
|
|
5032
|
+
tableName: "oauth2_clients";
|
|
5033
|
+
dataType: "object date";
|
|
5034
|
+
data: Date;
|
|
5035
|
+
driverParam: string;
|
|
5036
|
+
notNull: false;
|
|
5037
|
+
hasDefault: false;
|
|
5038
|
+
isPrimaryKey: false;
|
|
5039
|
+
isAutoincrement: false;
|
|
5040
|
+
hasRuntimeDefault: false;
|
|
5041
|
+
enumValues: undefined;
|
|
5042
|
+
identity: undefined;
|
|
5043
|
+
generated: undefined;
|
|
5044
|
+
}>;
|
|
5045
|
+
};
|
|
5046
|
+
dialect: "pg";
|
|
5047
|
+
}>;
|
|
5048
|
+
type OAuth2Client = typeof oauth2Clients.$inferSelect;
|
|
5049
|
+
type NewOAuth2Client = typeof oauth2Clients.$inferInsert;
|
|
5050
|
+
|
|
5051
|
+
/**
|
|
5052
|
+
* @spfn/auth - OAuth 2.1 Grants Entity
|
|
5053
|
+
*
|
|
5054
|
+
* What a user actually consented to: this client, for this resource, with these
|
|
5055
|
+
* scopes. Codes and tokens all hang off a grant, so it is the one row that has
|
|
5056
|
+
* to be revoked to cut a CLI off — `GET /_auth/oauth2/grants` lists them and
|
|
5057
|
+
* `DELETE /_auth/oauth2/grants/:id` is the button.
|
|
5058
|
+
*
|
|
5059
|
+
* One row per (client, user, resource). Re-consenting updates `scopes` rather
|
|
5060
|
+
* than adding a second row, so a user who widens a CLI's access sees one entry
|
|
5061
|
+
* for it and not a pile of them, and revoking that entry revokes everything the
|
|
5062
|
+
* client holds.
|
|
5063
|
+
*
|
|
5064
|
+
* A global revocation — revoke-all, a password change, a password reset, a
|
|
5065
|
+
* deletion request — revokes every grant the user has, for the reason device
|
|
5066
|
+
* codes are refused in the same breath: a live grant still has a refresh token
|
|
5067
|
+
* against it, and "sign me out everywhere" that leaves one alive has not.
|
|
5068
|
+
*/
|
|
5069
|
+
declare const oauth2Grants: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
5070
|
+
name: "oauth2_grants";
|
|
5071
|
+
schema: string;
|
|
5072
|
+
columns: {
|
|
5073
|
+
createdAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_grants", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>, {
|
|
5074
|
+
name: string;
|
|
5075
|
+
tableName: "oauth2_grants";
|
|
5076
|
+
dataType: "object date";
|
|
5077
|
+
data: Date;
|
|
5078
|
+
driverParam: string;
|
|
5079
|
+
notNull: true;
|
|
5080
|
+
hasDefault: true;
|
|
5081
|
+
isPrimaryKey: false;
|
|
5082
|
+
isAutoincrement: false;
|
|
5083
|
+
hasRuntimeDefault: false;
|
|
5084
|
+
enumValues: undefined;
|
|
5085
|
+
identity: undefined;
|
|
5086
|
+
generated: undefined;
|
|
5087
|
+
}>;
|
|
5088
|
+
updatedAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_grants", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>>, {
|
|
5089
|
+
name: string;
|
|
5090
|
+
tableName: "oauth2_grants";
|
|
5091
|
+
dataType: "object date";
|
|
5092
|
+
data: Date;
|
|
5093
|
+
driverParam: string;
|
|
5094
|
+
notNull: true;
|
|
5095
|
+
hasDefault: true;
|
|
5096
|
+
isPrimaryKey: false;
|
|
5097
|
+
isAutoincrement: false;
|
|
5098
|
+
hasRuntimeDefault: false;
|
|
5099
|
+
enumValues: undefined;
|
|
5100
|
+
identity: undefined;
|
|
5101
|
+
generated: undefined;
|
|
5102
|
+
}>;
|
|
5103
|
+
id: drizzle_orm_pg_core.PgBuildColumn<"oauth2_grants", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>, {
|
|
5104
|
+
name: string;
|
|
5105
|
+
tableName: "oauth2_grants";
|
|
5106
|
+
dataType: "number int53";
|
|
5107
|
+
data: number;
|
|
5108
|
+
driverParam: number;
|
|
5109
|
+
notNull: true;
|
|
5110
|
+
hasDefault: true;
|
|
5111
|
+
isPrimaryKey: false;
|
|
5112
|
+
isAutoincrement: false;
|
|
5113
|
+
hasRuntimeDefault: false;
|
|
5114
|
+
enumValues: undefined;
|
|
5115
|
+
identity: undefined;
|
|
5116
|
+
generated: undefined;
|
|
5117
|
+
}>;
|
|
5118
|
+
client: drizzle_orm_pg_core.PgBuildColumn<"oauth2_grants", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>, {
|
|
5119
|
+
name: string;
|
|
5120
|
+
tableName: "oauth2_grants";
|
|
5121
|
+
dataType: "number int53";
|
|
5122
|
+
data: number;
|
|
5123
|
+
driverParam: string | number;
|
|
5124
|
+
notNull: true;
|
|
5125
|
+
hasDefault: false;
|
|
5126
|
+
isPrimaryKey: false;
|
|
5127
|
+
isAutoincrement: false;
|
|
5128
|
+
hasRuntimeDefault: false;
|
|
5129
|
+
enumValues: undefined;
|
|
5130
|
+
identity: undefined;
|
|
5131
|
+
generated: undefined;
|
|
5132
|
+
}>;
|
|
5133
|
+
user: drizzle_orm_pg_core.PgBuildColumn<"oauth2_grants", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>, {
|
|
5134
|
+
name: string;
|
|
5135
|
+
tableName: "oauth2_grants";
|
|
5136
|
+
dataType: "number int53";
|
|
5137
|
+
data: number;
|
|
5138
|
+
driverParam: string | number;
|
|
5139
|
+
notNull: true;
|
|
5140
|
+
hasDefault: false;
|
|
5141
|
+
isPrimaryKey: false;
|
|
5142
|
+
isAutoincrement: false;
|
|
5143
|
+
hasRuntimeDefault: false;
|
|
5144
|
+
enumValues: undefined;
|
|
5145
|
+
identity: undefined;
|
|
5146
|
+
generated: undefined;
|
|
5147
|
+
}>;
|
|
5148
|
+
resource: drizzle_orm_pg_core.PgBuildColumn<"oauth2_grants", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
5149
|
+
name: string;
|
|
5150
|
+
tableName: "oauth2_grants";
|
|
5151
|
+
dataType: "string";
|
|
5152
|
+
data: string;
|
|
5153
|
+
driverParam: string;
|
|
5154
|
+
notNull: true;
|
|
5155
|
+
hasDefault: false;
|
|
5156
|
+
isPrimaryKey: false;
|
|
5157
|
+
isAutoincrement: false;
|
|
5158
|
+
hasRuntimeDefault: false;
|
|
5159
|
+
enumValues: undefined;
|
|
5160
|
+
identity: undefined;
|
|
5161
|
+
generated: undefined;
|
|
5162
|
+
}>;
|
|
5163
|
+
scopes: drizzle_orm_pg_core.PgBuildColumn<"oauth2_grants", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetDimensions<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, 1>>, {
|
|
5164
|
+
name: string;
|
|
5165
|
+
tableName: "oauth2_grants";
|
|
5166
|
+
dataType: "string";
|
|
5167
|
+
data: string[];
|
|
5168
|
+
driverParam: string | string[];
|
|
5169
|
+
notNull: true;
|
|
5170
|
+
hasDefault: false;
|
|
5171
|
+
isPrimaryKey: false;
|
|
5172
|
+
isAutoincrement: false;
|
|
5173
|
+
hasRuntimeDefault: false;
|
|
5174
|
+
enumValues: undefined;
|
|
5175
|
+
identity: undefined;
|
|
5176
|
+
generated: undefined;
|
|
5177
|
+
}>;
|
|
5178
|
+
revokedAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_grants", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
5179
|
+
name: string;
|
|
5180
|
+
tableName: "oauth2_grants";
|
|
5181
|
+
dataType: "object date";
|
|
5182
|
+
data: Date;
|
|
5183
|
+
driverParam: string;
|
|
5184
|
+
notNull: false;
|
|
5185
|
+
hasDefault: false;
|
|
5186
|
+
isPrimaryKey: false;
|
|
5187
|
+
isAutoincrement: false;
|
|
5188
|
+
hasRuntimeDefault: false;
|
|
5189
|
+
enumValues: undefined;
|
|
5190
|
+
identity: undefined;
|
|
5191
|
+
generated: undefined;
|
|
5192
|
+
}>;
|
|
5193
|
+
};
|
|
5194
|
+
dialect: "pg";
|
|
5195
|
+
}>;
|
|
5196
|
+
type OAuth2Grant = typeof oauth2Grants.$inferSelect;
|
|
5197
|
+
type NewOAuth2Grant = typeof oauth2Grants.$inferInsert;
|
|
5198
|
+
|
|
5199
|
+
/**
|
|
5200
|
+
* @spfn/auth - OAuth 2.1 Authorization Codes Entity
|
|
5201
|
+
*
|
|
5202
|
+
* The 60-second, single-use ticket the consent screen hands back through the
|
|
5203
|
+
* client's own loopback listener, exchanged at the token endpoint for tokens.
|
|
5204
|
+
*
|
|
5205
|
+
* Only the SHA-256 hash is stored, as with device codes, ops tokens and signup
|
|
5206
|
+
* links: a dump of this table must not let its reader finish somebody's
|
|
5207
|
+
* authorization. The unique constraint on the hash doubles as the lookup index.
|
|
5208
|
+
*
|
|
5209
|
+
* `usedAt` is written by the same statement that reads the row, so of two token
|
|
5210
|
+
* requests arriving together exactly one gets tokens. A code presented twice is
|
|
5211
|
+
* the signature of a stolen code, and the second presentation revokes the whole
|
|
5212
|
+
* grant rather than merely refusing — by then the thief may already hold what
|
|
5213
|
+
* the first exchange produced.
|
|
5214
|
+
*/
|
|
5215
|
+
declare const oauth2AuthorizationCodes: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
5216
|
+
name: "oauth2_authorization_codes";
|
|
5217
|
+
schema: string;
|
|
5218
|
+
columns: {
|
|
5219
|
+
createdAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_authorization_codes", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>, {
|
|
5220
|
+
name: string;
|
|
5221
|
+
tableName: "oauth2_authorization_codes";
|
|
5222
|
+
dataType: "object date";
|
|
5223
|
+
data: Date;
|
|
5224
|
+
driverParam: string;
|
|
5225
|
+
notNull: true;
|
|
5226
|
+
hasDefault: true;
|
|
5227
|
+
isPrimaryKey: false;
|
|
5228
|
+
isAutoincrement: false;
|
|
5229
|
+
hasRuntimeDefault: false;
|
|
5230
|
+
enumValues: undefined;
|
|
5231
|
+
identity: undefined;
|
|
5232
|
+
generated: undefined;
|
|
5233
|
+
}>;
|
|
5234
|
+
updatedAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_authorization_codes", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>>, {
|
|
5235
|
+
name: string;
|
|
5236
|
+
tableName: "oauth2_authorization_codes";
|
|
5237
|
+
dataType: "object date";
|
|
5238
|
+
data: Date;
|
|
5239
|
+
driverParam: string;
|
|
5240
|
+
notNull: true;
|
|
5241
|
+
hasDefault: true;
|
|
5242
|
+
isPrimaryKey: false;
|
|
5243
|
+
isAutoincrement: false;
|
|
5244
|
+
hasRuntimeDefault: false;
|
|
5245
|
+
enumValues: undefined;
|
|
5246
|
+
identity: undefined;
|
|
5247
|
+
generated: undefined;
|
|
5248
|
+
}>;
|
|
5249
|
+
id: drizzle_orm_pg_core.PgBuildColumn<"oauth2_authorization_codes", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>, {
|
|
5250
|
+
name: string;
|
|
5251
|
+
tableName: "oauth2_authorization_codes";
|
|
5252
|
+
dataType: "number int53";
|
|
5253
|
+
data: number;
|
|
5254
|
+
driverParam: number;
|
|
5255
|
+
notNull: true;
|
|
5256
|
+
hasDefault: true;
|
|
5257
|
+
isPrimaryKey: false;
|
|
5258
|
+
isAutoincrement: false;
|
|
5259
|
+
hasRuntimeDefault: false;
|
|
5260
|
+
enumValues: undefined;
|
|
5261
|
+
identity: undefined;
|
|
5262
|
+
generated: undefined;
|
|
5263
|
+
}>;
|
|
5264
|
+
codeHash: drizzle_orm_pg_core.PgBuildColumn<"oauth2_authorization_codes", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
5265
|
+
name: string;
|
|
5266
|
+
tableName: "oauth2_authorization_codes";
|
|
5267
|
+
dataType: "string";
|
|
5268
|
+
data: string;
|
|
5269
|
+
driverParam: string;
|
|
5270
|
+
notNull: true;
|
|
5271
|
+
hasDefault: false;
|
|
5272
|
+
isPrimaryKey: false;
|
|
5273
|
+
isAutoincrement: false;
|
|
5274
|
+
hasRuntimeDefault: false;
|
|
5275
|
+
enumValues: undefined;
|
|
5276
|
+
identity: undefined;
|
|
5277
|
+
generated: undefined;
|
|
5278
|
+
}>;
|
|
5279
|
+
grant: drizzle_orm_pg_core.PgBuildColumn<"oauth2_authorization_codes", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>, {
|
|
5280
|
+
name: string;
|
|
5281
|
+
tableName: "oauth2_authorization_codes";
|
|
5282
|
+
dataType: "number int53";
|
|
5283
|
+
data: number;
|
|
5284
|
+
driverParam: string | number;
|
|
5285
|
+
notNull: true;
|
|
5286
|
+
hasDefault: false;
|
|
5287
|
+
isPrimaryKey: false;
|
|
5288
|
+
isAutoincrement: false;
|
|
5289
|
+
hasRuntimeDefault: false;
|
|
5290
|
+
enumValues: undefined;
|
|
5291
|
+
identity: undefined;
|
|
5292
|
+
generated: undefined;
|
|
5293
|
+
}>;
|
|
5294
|
+
redirectUri: drizzle_orm_pg_core.PgBuildColumn<"oauth2_authorization_codes", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
5295
|
+
name: string;
|
|
5296
|
+
tableName: "oauth2_authorization_codes";
|
|
5297
|
+
dataType: "string";
|
|
5298
|
+
data: string;
|
|
5299
|
+
driverParam: string;
|
|
5300
|
+
notNull: true;
|
|
5301
|
+
hasDefault: false;
|
|
5302
|
+
isPrimaryKey: false;
|
|
5303
|
+
isAutoincrement: false;
|
|
5304
|
+
hasRuntimeDefault: false;
|
|
5305
|
+
enumValues: undefined;
|
|
5306
|
+
identity: undefined;
|
|
5307
|
+
generated: undefined;
|
|
5308
|
+
}>;
|
|
5309
|
+
codeChallenge: drizzle_orm_pg_core.PgBuildColumn<"oauth2_authorization_codes", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
5310
|
+
name: string;
|
|
5311
|
+
tableName: "oauth2_authorization_codes";
|
|
5312
|
+
dataType: "string";
|
|
5313
|
+
data: string;
|
|
5314
|
+
driverParam: string;
|
|
5315
|
+
notNull: true;
|
|
5316
|
+
hasDefault: false;
|
|
5317
|
+
isPrimaryKey: false;
|
|
5318
|
+
isAutoincrement: false;
|
|
5319
|
+
hasRuntimeDefault: false;
|
|
5320
|
+
enumValues: undefined;
|
|
5321
|
+
identity: undefined;
|
|
5322
|
+
generated: undefined;
|
|
5323
|
+
}>;
|
|
5324
|
+
expiresAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_authorization_codes", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTimestampBuilder>, {
|
|
5325
|
+
name: string;
|
|
5326
|
+
tableName: "oauth2_authorization_codes";
|
|
5327
|
+
dataType: "object date";
|
|
5328
|
+
data: Date;
|
|
5329
|
+
driverParam: string;
|
|
5330
|
+
notNull: true;
|
|
5331
|
+
hasDefault: false;
|
|
5332
|
+
isPrimaryKey: false;
|
|
5333
|
+
isAutoincrement: false;
|
|
5334
|
+
hasRuntimeDefault: false;
|
|
5335
|
+
enumValues: undefined;
|
|
5336
|
+
identity: undefined;
|
|
5337
|
+
generated: undefined;
|
|
5338
|
+
}>;
|
|
5339
|
+
usedAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_authorization_codes", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
5340
|
+
name: string;
|
|
5341
|
+
tableName: "oauth2_authorization_codes";
|
|
5342
|
+
dataType: "object date";
|
|
5343
|
+
data: Date;
|
|
5344
|
+
driverParam: string;
|
|
5345
|
+
notNull: false;
|
|
5346
|
+
hasDefault: false;
|
|
5347
|
+
isPrimaryKey: false;
|
|
5348
|
+
isAutoincrement: false;
|
|
5349
|
+
hasRuntimeDefault: false;
|
|
5350
|
+
enumValues: undefined;
|
|
5351
|
+
identity: undefined;
|
|
5352
|
+
generated: undefined;
|
|
5353
|
+
}>;
|
|
5354
|
+
};
|
|
5355
|
+
dialect: "pg";
|
|
5356
|
+
}>;
|
|
5357
|
+
type OAuth2AuthorizationCode = typeof oauth2AuthorizationCodes.$inferSelect;
|
|
5358
|
+
type NewOAuth2AuthorizationCode = typeof oauth2AuthorizationCodes.$inferInsert;
|
|
5359
|
+
|
|
5360
|
+
/**
|
|
5361
|
+
* @spfn/auth - OAuth 2.1 Tokens Entity
|
|
5362
|
+
*
|
|
5363
|
+
* Access and refresh tokens alike, one row each, distinguished by `kind`. Both
|
|
5364
|
+
* are opaque — `spfn_at_<64 hex>` and `spfn_rt_<64 hex>` — because whatever
|
|
5365
|
+
* verifies them is in the same process as this table, which is the same reason
|
|
5366
|
+
* ops tokens, device codes and signup links are opaque. A JWT would buy
|
|
5367
|
+
* stateless verification and pay for it by making revocation take effect only
|
|
5368
|
+
* at expiry, and revocation here is a button the user presses.
|
|
5369
|
+
*
|
|
5370
|
+
* Only the SHA-256 hash is stored. The value exists in the clear exactly once,
|
|
5371
|
+
* in the token response.
|
|
5372
|
+
*
|
|
5373
|
+
* `replacedAt` is what makes refresh rotation detectable: a rotated refresh is
|
|
5374
|
+
* not deleted, it is marked, so presenting it again is distinguishable from
|
|
5375
|
+
* presenting one that never existed. That presentation means the token leaked —
|
|
5376
|
+
* either the client or the thief is replaying — and the answer is to revoke the
|
|
5377
|
+
* grant, which kills the replacement as well.
|
|
5378
|
+
*/
|
|
5379
|
+
/**
|
|
5380
|
+
* Which half of the pair a row is.
|
|
5381
|
+
*
|
|
5382
|
+
* One table rather than two: every rule that matters — revoked, expired, whose
|
|
5383
|
+
* grant, which scopes — is the same for both, and a single `kind` column keeps
|
|
5384
|
+
* "revoke everything under this grant" one statement.
|
|
5385
|
+
*/
|
|
5386
|
+
declare const OAUTH2_TOKEN_KINDS: readonly ["access", "refresh"];
|
|
5387
|
+
type OAuth2TokenKind = typeof OAUTH2_TOKEN_KINDS[number];
|
|
5388
|
+
declare const oauth2Tokens: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
5389
|
+
name: "oauth2_tokens";
|
|
5390
|
+
schema: string;
|
|
5391
|
+
columns: {
|
|
5392
|
+
createdAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_tokens", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>, {
|
|
5393
|
+
name: string;
|
|
5394
|
+
tableName: "oauth2_tokens";
|
|
5395
|
+
dataType: "object date";
|
|
5396
|
+
data: Date;
|
|
5397
|
+
driverParam: string;
|
|
5398
|
+
notNull: true;
|
|
5399
|
+
hasDefault: true;
|
|
5400
|
+
isPrimaryKey: false;
|
|
5401
|
+
isAutoincrement: false;
|
|
5402
|
+
hasRuntimeDefault: false;
|
|
5403
|
+
enumValues: undefined;
|
|
5404
|
+
identity: undefined;
|
|
5405
|
+
generated: undefined;
|
|
5406
|
+
}>;
|
|
5407
|
+
updatedAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_tokens", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>>, {
|
|
5408
|
+
name: string;
|
|
5409
|
+
tableName: "oauth2_tokens";
|
|
5410
|
+
dataType: "object date";
|
|
5411
|
+
data: Date;
|
|
5412
|
+
driverParam: string;
|
|
5413
|
+
notNull: true;
|
|
5414
|
+
hasDefault: true;
|
|
5415
|
+
isPrimaryKey: false;
|
|
5416
|
+
isAutoincrement: false;
|
|
5417
|
+
hasRuntimeDefault: false;
|
|
5418
|
+
enumValues: undefined;
|
|
5419
|
+
identity: undefined;
|
|
5420
|
+
generated: undefined;
|
|
5421
|
+
}>;
|
|
5422
|
+
id: drizzle_orm_pg_core.PgBuildColumn<"oauth2_tokens", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>, {
|
|
5423
|
+
name: string;
|
|
5424
|
+
tableName: "oauth2_tokens";
|
|
5425
|
+
dataType: "number int53";
|
|
5426
|
+
data: number;
|
|
5427
|
+
driverParam: number;
|
|
5428
|
+
notNull: true;
|
|
5429
|
+
hasDefault: true;
|
|
5430
|
+
isPrimaryKey: false;
|
|
5431
|
+
isAutoincrement: false;
|
|
5432
|
+
hasRuntimeDefault: false;
|
|
5433
|
+
enumValues: undefined;
|
|
5434
|
+
identity: undefined;
|
|
5435
|
+
generated: undefined;
|
|
5436
|
+
}>;
|
|
5437
|
+
tokenHash: drizzle_orm_pg_core.PgBuildColumn<"oauth2_tokens", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
|
|
5438
|
+
name: string;
|
|
5439
|
+
tableName: "oauth2_tokens";
|
|
5440
|
+
dataType: "string";
|
|
5441
|
+
data: string;
|
|
5442
|
+
driverParam: string;
|
|
5443
|
+
notNull: true;
|
|
5444
|
+
hasDefault: false;
|
|
5445
|
+
isPrimaryKey: false;
|
|
5446
|
+
isAutoincrement: false;
|
|
5447
|
+
hasRuntimeDefault: false;
|
|
5448
|
+
enumValues: undefined;
|
|
5449
|
+
identity: undefined;
|
|
5450
|
+
generated: undefined;
|
|
5451
|
+
}>;
|
|
5452
|
+
kind: drizzle_orm_pg_core.PgBuildColumn<"oauth2_tokens", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<["access", "refresh"] & [string, ...string[]]>>, {
|
|
5453
|
+
name: string;
|
|
5454
|
+
tableName: "oauth2_tokens";
|
|
5455
|
+
dataType: "string enum";
|
|
5456
|
+
data: "access" | "refresh";
|
|
5457
|
+
driverParam: string;
|
|
5458
|
+
notNull: true;
|
|
5459
|
+
hasDefault: false;
|
|
5460
|
+
isPrimaryKey: false;
|
|
5461
|
+
isAutoincrement: false;
|
|
5462
|
+
hasRuntimeDefault: false;
|
|
5463
|
+
enumValues: ["access", "refresh"] & [string, ...string[]];
|
|
5464
|
+
identity: undefined;
|
|
5465
|
+
generated: undefined;
|
|
5466
|
+
}>;
|
|
5467
|
+
grant: drizzle_orm_pg_core.PgBuildColumn<"oauth2_tokens", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>, {
|
|
5468
|
+
name: string;
|
|
5469
|
+
tableName: "oauth2_tokens";
|
|
5470
|
+
dataType: "number int53";
|
|
5471
|
+
data: number;
|
|
5472
|
+
driverParam: string | number;
|
|
5473
|
+
notNull: true;
|
|
5474
|
+
hasDefault: false;
|
|
5475
|
+
isPrimaryKey: false;
|
|
5476
|
+
isAutoincrement: false;
|
|
5477
|
+
hasRuntimeDefault: false;
|
|
5478
|
+
enumValues: undefined;
|
|
5479
|
+
identity: undefined;
|
|
5480
|
+
generated: undefined;
|
|
5481
|
+
}>;
|
|
5482
|
+
scopes: drizzle_orm_pg_core.PgBuildColumn<"oauth2_tokens", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetDimensions<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, 1>>, {
|
|
5483
|
+
name: string;
|
|
5484
|
+
tableName: "oauth2_tokens";
|
|
5485
|
+
dataType: "string";
|
|
5486
|
+
data: string[];
|
|
5487
|
+
driverParam: string | string[];
|
|
5488
|
+
notNull: true;
|
|
5489
|
+
hasDefault: false;
|
|
5490
|
+
isPrimaryKey: false;
|
|
5491
|
+
isAutoincrement: false;
|
|
5492
|
+
hasRuntimeDefault: false;
|
|
5493
|
+
enumValues: undefined;
|
|
5494
|
+
identity: undefined;
|
|
5495
|
+
generated: undefined;
|
|
5496
|
+
}>;
|
|
5497
|
+
expiresAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_tokens", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTimestampBuilder>, {
|
|
5498
|
+
name: string;
|
|
5499
|
+
tableName: "oauth2_tokens";
|
|
5500
|
+
dataType: "object date";
|
|
5501
|
+
data: Date;
|
|
5502
|
+
driverParam: string;
|
|
5503
|
+
notNull: true;
|
|
5504
|
+
hasDefault: false;
|
|
5505
|
+
isPrimaryKey: false;
|
|
5506
|
+
isAutoincrement: false;
|
|
5507
|
+
hasRuntimeDefault: false;
|
|
5508
|
+
enumValues: undefined;
|
|
5509
|
+
identity: undefined;
|
|
5510
|
+
generated: undefined;
|
|
5511
|
+
}>;
|
|
5512
|
+
revokedAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_tokens", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
5513
|
+
name: string;
|
|
5514
|
+
tableName: "oauth2_tokens";
|
|
5515
|
+
dataType: "object date";
|
|
5516
|
+
data: Date;
|
|
5517
|
+
driverParam: string;
|
|
5518
|
+
notNull: false;
|
|
5519
|
+
hasDefault: false;
|
|
5520
|
+
isPrimaryKey: false;
|
|
5521
|
+
isAutoincrement: false;
|
|
5522
|
+
hasRuntimeDefault: false;
|
|
5523
|
+
enumValues: undefined;
|
|
5524
|
+
identity: undefined;
|
|
5525
|
+
generated: undefined;
|
|
5526
|
+
}>;
|
|
5527
|
+
replacedAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_tokens", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
5528
|
+
name: string;
|
|
5529
|
+
tableName: "oauth2_tokens";
|
|
5530
|
+
dataType: "object date";
|
|
5531
|
+
data: Date;
|
|
5532
|
+
driverParam: string;
|
|
5533
|
+
notNull: false;
|
|
5534
|
+
hasDefault: false;
|
|
5535
|
+
isPrimaryKey: false;
|
|
5536
|
+
isAutoincrement: false;
|
|
5537
|
+
hasRuntimeDefault: false;
|
|
5538
|
+
enumValues: undefined;
|
|
5539
|
+
identity: undefined;
|
|
5540
|
+
generated: undefined;
|
|
5541
|
+
}>;
|
|
5542
|
+
lastUsedAt: drizzle_orm_pg_core.PgBuildColumn<"oauth2_tokens", drizzle_orm_pg_core.PgTimestampBuilder, {
|
|
5543
|
+
name: string;
|
|
5544
|
+
tableName: "oauth2_tokens";
|
|
5545
|
+
dataType: "object date";
|
|
5546
|
+
data: Date;
|
|
5547
|
+
driverParam: string;
|
|
5548
|
+
notNull: false;
|
|
5549
|
+
hasDefault: false;
|
|
5550
|
+
isPrimaryKey: false;
|
|
5551
|
+
isAutoincrement: false;
|
|
5552
|
+
hasRuntimeDefault: false;
|
|
5553
|
+
enumValues: undefined;
|
|
5554
|
+
identity: undefined;
|
|
5555
|
+
generated: undefined;
|
|
5556
|
+
}>;
|
|
5557
|
+
};
|
|
5558
|
+
dialect: "pg";
|
|
5559
|
+
}>;
|
|
5560
|
+
type OAuth2Token = typeof oauth2Tokens.$inferSelect;
|
|
5561
|
+
type NewOAuth2Token = typeof oauth2Tokens.$inferInsert;
|
|
5562
|
+
|
|
3485
5563
|
/**
|
|
3486
5564
|
* @spfn/auth - Auth Metadata Entity
|
|
3487
5565
|
*
|
|
@@ -3607,10 +5685,56 @@ declare class UsersRepository extends BaseRepository {
|
|
|
3607
5685
|
lastLoginAt: Date | null;
|
|
3608
5686
|
}>;
|
|
3609
5687
|
/**
|
|
3610
|
-
*
|
|
3611
|
-
*
|
|
5688
|
+
* Take a row lock on a user, so callers that read-then-decide serialise.
|
|
5689
|
+
*
|
|
5690
|
+
* `SELECT id ... FOR UPDATE` and nothing else: the value is never used, the
|
|
5691
|
+
* lock is. A caller that counts an account's remaining credentials and then
|
|
5692
|
+
* removes one has a read-modify-write between two statements, and two such
|
|
5693
|
+
* callers racing both read the pre-removal count. Locking the owner row
|
|
5694
|
+
* first makes the second one wait for the first to commit, so it counts
|
|
5695
|
+
* what is actually left.
|
|
5696
|
+
*
|
|
5697
|
+
* Only meaningful inside a transaction — the lock is released at commit.
|
|
5698
|
+
* Write primary.
|
|
5699
|
+
*/
|
|
5700
|
+
lockById(id: number): Promise<void>;
|
|
5701
|
+
/**
|
|
5702
|
+
* 이메일로 사용자 조회
|
|
5703
|
+
* Read replica 사용
|
|
5704
|
+
*/
|
|
5705
|
+
findByEmail(email: string): Promise<{
|
|
5706
|
+
deletedAt: Date | null;
|
|
5707
|
+
deletedBy: string | null;
|
|
5708
|
+
createdAt: Date;
|
|
5709
|
+
updatedAt: Date;
|
|
5710
|
+
id: number;
|
|
5711
|
+
publicId: string;
|
|
5712
|
+
email: string | null;
|
|
5713
|
+
phone: string | null;
|
|
5714
|
+
username: string | null;
|
|
5715
|
+
passwordHash: string | null;
|
|
5716
|
+
passwordChangeRequired: boolean;
|
|
5717
|
+
roleId: number;
|
|
5718
|
+
status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
|
|
5719
|
+
emailVerifiedAt: Date | null;
|
|
5720
|
+
phoneVerifiedAt: Date | null;
|
|
5721
|
+
lastLoginAt: Date | null;
|
|
5722
|
+
}>;
|
|
5723
|
+
/**
|
|
5724
|
+
* 이메일로 사용자 조회 — 저장된 형태와 무관하게 찾는다.
|
|
5725
|
+
*
|
|
5726
|
+
* `findByEmail` asks whether a row holds this exact address; this asks
|
|
5727
|
+
* whether any row *is* this address, whatever form it was written in. The
|
|
5728
|
+
* difference matters to a caller that answers "no" by creating an account:
|
|
5729
|
+
* a lookup that misses a row stored in another form would make a second
|
|
5730
|
+
* account for a person who already has one, and the unique constraint
|
|
5731
|
+
* cannot object because the two stored strings differ.
|
|
5732
|
+
*
|
|
5733
|
+
* Folding in the predicate means no index on `email` applies, so this is for
|
|
5734
|
+
* the few addresses a caller decides about — admin seeding — not for a
|
|
5735
|
+
* request path. Write primary: the answer decides whether to insert.
|
|
3612
5736
|
*/
|
|
3613
|
-
|
|
5737
|
+
findByEmailInAnyStoredForm(email: string): Promise<{
|
|
3614
5738
|
deletedAt: Date | null;
|
|
3615
5739
|
deletedBy: string | null;
|
|
3616
5740
|
createdAt: Date;
|
|
@@ -3754,21 +5878,66 @@ declare class UsersRepository extends BaseRepository {
|
|
|
3754
5878
|
create(data: NewUser): Promise<{
|
|
3755
5879
|
email: string | null;
|
|
3756
5880
|
phone: string | null;
|
|
3757
|
-
|
|
5881
|
+
status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
|
|
3758
5882
|
publicId: string;
|
|
5883
|
+
passwordChangeRequired: boolean;
|
|
5884
|
+
id: number;
|
|
3759
5885
|
username: string | null;
|
|
3760
5886
|
passwordHash: string | null;
|
|
3761
|
-
passwordChangeRequired: boolean;
|
|
3762
5887
|
roleId: number;
|
|
3763
5888
|
createdAt: Date;
|
|
3764
5889
|
updatedAt: Date;
|
|
3765
|
-
status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
|
|
3766
5890
|
emailVerifiedAt: Date | null;
|
|
3767
5891
|
phoneVerifiedAt: Date | null;
|
|
3768
5892
|
lastLoginAt: Date | null;
|
|
3769
5893
|
deletedAt: Date | null;
|
|
3770
5894
|
deletedBy: string | null;
|
|
3771
5895
|
}>;
|
|
5896
|
+
/**
|
|
5897
|
+
* User ids grouped by an address two or more rows share once folded.
|
|
5898
|
+
*
|
|
5899
|
+
* The whole comparison happens in the database and only the colliding groups
|
|
5900
|
+
* come back, so the size of the answer is the size of the problem rather
|
|
5901
|
+
* than the size of the table. `users.email` is unique, so a group of more
|
|
5902
|
+
* than one can only be rows that differ by capitalization or padding —
|
|
5903
|
+
* exactly the ones a rewrite cannot decide between.
|
|
5904
|
+
*
|
|
5905
|
+
* Every member id is returned, including a row already holding the
|
|
5906
|
+
* canonical form, because the operator has to compare the accounts against
|
|
5907
|
+
* each other to settle which is real.
|
|
5908
|
+
*
|
|
5909
|
+
* Write primary: the caller is about to rewrite rows and a replica could
|
|
5910
|
+
* still be showing the pre-fix state.
|
|
5911
|
+
*/
|
|
5912
|
+
findEmailConflictGroups(): Promise<number[][]>;
|
|
5913
|
+
/**
|
|
5914
|
+
* Fold every stored address to canonical form, leaving the given ids alone.
|
|
5915
|
+
*
|
|
5916
|
+
* One statement rather than a row at a time: the rewrite is the same
|
|
5917
|
+
* expression the detection uses, so the database can do it in place. A
|
|
5918
|
+
* legacy install with a large users table therefore pays one update instead
|
|
5919
|
+
* of a round trip per row on the boot path, and no list of addresses is ever
|
|
5920
|
+
* carried through the application.
|
|
5921
|
+
*
|
|
5922
|
+
* The excluded ids travel as a single array parameter, so the count of
|
|
5923
|
+
* conflicts cannot run into the protocol's limit on bind parameters.
|
|
5924
|
+
*
|
|
5925
|
+
* One statement also means all or nothing. `users.email` is unique, so if an
|
|
5926
|
+
* instance still running the old code registers a canonical address in the
|
|
5927
|
+
* moment between the conflict query and this update, the update aborts and
|
|
5928
|
+
* nothing is folded on this boot. The next boot sees that pair as a conflict
|
|
5929
|
+
* and folds everything else, so the repair is deferred rather than lost.
|
|
5930
|
+
*
|
|
5931
|
+
* `lower(btrim(...))` is the SQL spelling of `normalizeEmail`. The two agree
|
|
5932
|
+
* on every address this package's validation accepts (ASCII, no interior
|
|
5933
|
+
* whitespace); an address outside that set — reachable only by an app
|
|
5934
|
+
* writing to the repository directly — may fold differently in a database
|
|
5935
|
+
* whose collation lower-cases non-ASCII letters.
|
|
5936
|
+
*
|
|
5937
|
+
* @param excludedIds - Rows to leave untouched, normally the conflict groups
|
|
5938
|
+
* @returns How many rows were rewritten
|
|
5939
|
+
*/
|
|
5940
|
+
normalizeEmailsExcept(excludedIds: number[]): Promise<number>;
|
|
3772
5941
|
/**
|
|
3773
5942
|
* 사용자 정보 업데이트
|
|
3774
5943
|
* Write primary 사용
|
|
@@ -3869,15 +6038,15 @@ declare class UsersRepository extends BaseRepository {
|
|
|
3869
6038
|
deleteById(id: number): Promise<{
|
|
3870
6039
|
email: string | null;
|
|
3871
6040
|
phone: string | null;
|
|
3872
|
-
|
|
6041
|
+
status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
|
|
3873
6042
|
publicId: string;
|
|
6043
|
+
passwordChangeRequired: boolean;
|
|
6044
|
+
id: number;
|
|
3874
6045
|
username: string | null;
|
|
3875
6046
|
passwordHash: string | null;
|
|
3876
|
-
passwordChangeRequired: boolean;
|
|
3877
6047
|
roleId: number;
|
|
3878
6048
|
createdAt: Date;
|
|
3879
6049
|
updatedAt: Date;
|
|
3880
|
-
status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
|
|
3881
6050
|
emailVerifiedAt: Date | null;
|
|
3882
6051
|
phoneVerifiedAt: Date | null;
|
|
3883
6052
|
lastLoginAt: Date | null;
|
|
@@ -4307,125 +6476,538 @@ declare class KeysRepository extends BaseRepository {
|
|
|
4307
6476
|
*/
|
|
4308
6477
|
updateLastUsedById(id: number, identity?: ClientIdentity | null): Promise<void>;
|
|
4309
6478
|
}
|
|
4310
|
-
declare const keysRepository: KeysRepository;
|
|
6479
|
+
declare const keysRepository: KeysRepository;
|
|
6480
|
+
|
|
6481
|
+
/**
|
|
6482
|
+
* Verification Codes Repository
|
|
6483
|
+
*
|
|
6484
|
+
* 인증 코드 관리를 위한 Repository
|
|
6485
|
+
* BaseRepository를 상속받아 자동 트랜잭션 컨텍스트 지원 및 Read/Write 분리
|
|
6486
|
+
*/
|
|
6487
|
+
|
|
6488
|
+
/**
|
|
6489
|
+
* Verification Codes Repository 클래스
|
|
6490
|
+
*
|
|
6491
|
+
* BaseRepository를 상속받아 다음 기능을 제공:
|
|
6492
|
+
* - 자동 트랜잭션 컨텍스트 감지 및 사용
|
|
6493
|
+
* - Read/Write 연결 분리 (replica 활용)
|
|
6494
|
+
* - 타입 안전성
|
|
6495
|
+
*/
|
|
6496
|
+
declare class VerificationCodesRepository extends BaseRepository {
|
|
6497
|
+
/**
|
|
6498
|
+
* Target과 Purpose로 유효한 인증 코드 조회
|
|
6499
|
+
* (만료되지 않고, 사용되지 않은 코드)
|
|
6500
|
+
* Read replica 사용
|
|
6501
|
+
*/
|
|
6502
|
+
findValidByTargetAndPurpose(target: string, purpose: VerificationPurpose): Promise<{
|
|
6503
|
+
createdAt: Date;
|
|
6504
|
+
updatedAt: Date;
|
|
6505
|
+
id: number;
|
|
6506
|
+
target: string;
|
|
6507
|
+
targetType: "email" | "phone";
|
|
6508
|
+
code: string;
|
|
6509
|
+
purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
|
|
6510
|
+
expiresAt: Date;
|
|
6511
|
+
usedAt: Date | null;
|
|
6512
|
+
attempts: number;
|
|
6513
|
+
}>;
|
|
6514
|
+
/**
|
|
6515
|
+
* ID로 인증 코드 조회
|
|
6516
|
+
* Read replica 사용
|
|
6517
|
+
*/
|
|
6518
|
+
findById(id: number): Promise<{
|
|
6519
|
+
createdAt: Date;
|
|
6520
|
+
updatedAt: Date;
|
|
6521
|
+
id: number;
|
|
6522
|
+
target: string;
|
|
6523
|
+
targetType: "email" | "phone";
|
|
6524
|
+
code: string;
|
|
6525
|
+
purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
|
|
6526
|
+
expiresAt: Date;
|
|
6527
|
+
usedAt: Date | null;
|
|
6528
|
+
attempts: number;
|
|
6529
|
+
}>;
|
|
6530
|
+
/**
|
|
6531
|
+
* 인증 코드 생성
|
|
6532
|
+
* Write primary 사용
|
|
6533
|
+
*/
|
|
6534
|
+
create(data: NewVerificationCode): Promise<{
|
|
6535
|
+
target: string;
|
|
6536
|
+
targetType: "email" | "phone";
|
|
6537
|
+
purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
|
|
6538
|
+
code: string;
|
|
6539
|
+
id: number;
|
|
6540
|
+
createdAt: Date;
|
|
6541
|
+
updatedAt: Date;
|
|
6542
|
+
expiresAt: Date;
|
|
6543
|
+
usedAt: Date | null;
|
|
6544
|
+
attempts: number;
|
|
6545
|
+
}>;
|
|
6546
|
+
/**
|
|
6547
|
+
* 인증 코드 사용 처리
|
|
6548
|
+
* Write primary 사용
|
|
6549
|
+
*/
|
|
6550
|
+
markAsUsed(id: number): Promise<{
|
|
6551
|
+
createdAt: Date;
|
|
6552
|
+
updatedAt: Date;
|
|
6553
|
+
id: number;
|
|
6554
|
+
target: string;
|
|
6555
|
+
targetType: "email" | "phone";
|
|
6556
|
+
code: string;
|
|
6557
|
+
purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
|
|
6558
|
+
expiresAt: Date;
|
|
6559
|
+
usedAt: Date | null;
|
|
6560
|
+
attempts: number;
|
|
6561
|
+
}>;
|
|
6562
|
+
/**
|
|
6563
|
+
* 시도 횟수 증가
|
|
6564
|
+
* Write primary 사용
|
|
6565
|
+
*/
|
|
6566
|
+
incrementAttempts(id: number): Promise<{
|
|
6567
|
+
createdAt: Date;
|
|
6568
|
+
updatedAt: Date;
|
|
6569
|
+
id: number;
|
|
6570
|
+
target: string;
|
|
6571
|
+
targetType: "email" | "phone";
|
|
6572
|
+
code: string;
|
|
6573
|
+
purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
|
|
6574
|
+
expiresAt: Date;
|
|
6575
|
+
usedAt: Date | null;
|
|
6576
|
+
attempts: number;
|
|
6577
|
+
} | null>;
|
|
6578
|
+
/**
|
|
6579
|
+
* 만료된 코드 삭제
|
|
6580
|
+
* Write primary 사용
|
|
6581
|
+
*/
|
|
6582
|
+
deleteExpired(): Promise<number>;
|
|
6583
|
+
/**
|
|
6584
|
+
* Target(email/phone)의 모든 인증 코드 삭제 (계정 파기용)
|
|
6585
|
+
*
|
|
6586
|
+
* verificationCodes는 userId FK가 없고 target 텍스트로만 연결되므로, 파기 시
|
|
6587
|
+
* 원래 email/phone을 이 메서드로 직접 정리해야 한다.
|
|
6588
|
+
* Write primary 사용
|
|
6589
|
+
*/
|
|
6590
|
+
deleteByTarget(target: string): Promise<number>;
|
|
6591
|
+
/**
|
|
6592
|
+
* Target의 모든 이전 코드 무효화 (새 코드 발급 시)
|
|
6593
|
+
* Write primary 사용
|
|
6594
|
+
*/
|
|
6595
|
+
invalidatePreviousCodes(target: string, purpose: VerificationPurpose): Promise<number>;
|
|
6596
|
+
}
|
|
6597
|
+
declare const verificationCodesRepository: VerificationCodesRepository;
|
|
6598
|
+
|
|
6599
|
+
/**
|
|
6600
|
+
* Signup Link Tokens Repository
|
|
6601
|
+
*
|
|
6602
|
+
* Data access for the verified-email signup flow. Extends BaseRepository for
|
|
6603
|
+
* transaction-context detection and read/write splitting.
|
|
6604
|
+
*
|
|
6605
|
+
* The two claim methods (`claimLink`, `claimSetupSession`) update conditionally
|
|
6606
|
+
* and return the row only if THIS call was the one that moved it. Two concurrent
|
|
6607
|
+
* confirms — or two concurrent password submits — therefore produce one winner
|
|
6608
|
+
* and one refusal, rather than both proceeding on a row they each read as
|
|
6609
|
+
* unclaimed. Read-then-write would lose that race.
|
|
6610
|
+
*/
|
|
6611
|
+
|
|
6612
|
+
declare class SignupLinkTokensRepository extends BaseRepository {
|
|
6613
|
+
/**
|
|
6614
|
+
* Create a signup link row.
|
|
6615
|
+
* Write primary.
|
|
6616
|
+
*/
|
|
6617
|
+
create(data: NewSignupLinkToken): Promise<SignupLinkToken>;
|
|
6618
|
+
/**
|
|
6619
|
+
* Create a signup link row that has no token yet.
|
|
6620
|
+
*
|
|
6621
|
+
* The link is minted by the `auth.link-mail` job, not by the request, so the
|
|
6622
|
+
* row starts without a hash and `issue` writes one. Both delivery modes take
|
|
6623
|
+
* this path, which is what keeps "a hash lands only through `issue`" true.
|
|
6624
|
+
*
|
|
6625
|
+
* Write primary.
|
|
6626
|
+
*/
|
|
6627
|
+
createPending(data: {
|
|
6628
|
+
email: string;
|
|
6629
|
+
returnPath: string | null;
|
|
6630
|
+
expiresAt: Date;
|
|
6631
|
+
}): Promise<SignupLinkToken>;
|
|
6632
|
+
/**
|
|
6633
|
+
* Write the token hash onto a pending row, but only if the link may still be
|
|
6634
|
+
* delivered: not consumed, not superseded, not completed, not expired.
|
|
6635
|
+
*
|
|
6636
|
+
* One statement rather than a read and a write, so a row superseded by a
|
|
6637
|
+
* newer request between the two never receives a credential — the worker
|
|
6638
|
+
* simply finds nothing to issue and sends no mail.
|
|
6639
|
+
*
|
|
6640
|
+
* @returns the issued row, or null if the row is no longer deliverable
|
|
6641
|
+
*/
|
|
6642
|
+
issue(id: number, tokenHash: string): Promise<SignupLinkToken | null>;
|
|
6643
|
+
/**
|
|
6644
|
+
* Find a row by the hash of an emailed token, in any state.
|
|
6645
|
+
*
|
|
6646
|
+
* Deliberately unfiltered: the caller decides why a link is refused, and a
|
|
6647
|
+
* row filtered out here would be indistinguishable from an unknown token in
|
|
6648
|
+
* the logs.
|
|
6649
|
+
*
|
|
6650
|
+
* Read replica.
|
|
6651
|
+
*/
|
|
6652
|
+
findByTokenHash(tokenHash: string): Promise<SignupLinkToken | null>;
|
|
6653
|
+
/**
|
|
6654
|
+
* Find a row by the hash of a setup session secret, in any state.
|
|
6655
|
+
* Read replica.
|
|
6656
|
+
*/
|
|
6657
|
+
findBySetupSecretHash(setupSecretHash: string): Promise<SignupLinkToken | null>;
|
|
6658
|
+
/**
|
|
6659
|
+
* Consume a link and open a setup session on it, but only if it is still
|
|
6660
|
+
* unconsumed and not superseded.
|
|
6661
|
+
*
|
|
6662
|
+
* @returns the updated row, or null if another request claimed it first
|
|
6663
|
+
*/
|
|
6664
|
+
claimLink(id: number, setupSecretHash: string, setupExpiresAt: Date): Promise<SignupLinkToken | null>;
|
|
6665
|
+
/**
|
|
6666
|
+
* Mark a setup session as completed, but only if it has not completed
|
|
6667
|
+
* already.
|
|
6668
|
+
*
|
|
6669
|
+
* @returns the updated row, or null if another request completed it first
|
|
6670
|
+
*/
|
|
6671
|
+
claimSetupSession(id: number): Promise<SignupLinkToken | null>;
|
|
6672
|
+
/**
|
|
6673
|
+
* Supersede every live link for an address, so requesting a new one
|
|
6674
|
+
* invalidates the previous one and any setup session opened from it.
|
|
6675
|
+
*
|
|
6676
|
+
* Write primary.
|
|
6677
|
+
*
|
|
6678
|
+
* @returns number of rows superseded
|
|
6679
|
+
*/
|
|
6680
|
+
supersedeLiveForEmail(email: string): Promise<number>;
|
|
6681
|
+
/**
|
|
6682
|
+
* Delete every signup link row for an address (account destruction).
|
|
6683
|
+
*
|
|
6684
|
+
* Rows key on the email text and carry no user FK, so destruction has to
|
|
6685
|
+
* clean them up by address the way verification codes are cleaned up.
|
|
6686
|
+
*
|
|
6687
|
+
* Write primary.
|
|
6688
|
+
*/
|
|
6689
|
+
deleteByEmail(email: string): Promise<number>;
|
|
6690
|
+
}
|
|
6691
|
+
declare const signupLinkTokensRepository: SignupLinkTokensRepository;
|
|
6692
|
+
|
|
6693
|
+
/**
|
|
6694
|
+
* Password Reset Tokens Repository
|
|
6695
|
+
*
|
|
6696
|
+
* Data access for the password reset flow. Extends BaseRepository for
|
|
6697
|
+
* transaction-context detection and read/write splitting.
|
|
6698
|
+
*
|
|
6699
|
+
* `consume` and `complete` update conditionally and return the row only if THIS
|
|
6700
|
+
* call was the one that moved it. Two concurrent confirms — or two concurrent
|
|
6701
|
+
* password submits — therefore produce one winner and one refusal, rather than
|
|
6702
|
+
* both proceeding on a row they each read as unclaimed. Read-then-write would
|
|
6703
|
+
* lose that race.
|
|
6704
|
+
*
|
|
6705
|
+
* The `findLive*` lookups filter to usable rows rather than returning anything
|
|
6706
|
+
* they find. Every refusal on this flow is the same 401 whatever caused it, so
|
|
6707
|
+
* there is nothing for a caller to learn from the difference — and a filter in
|
|
6708
|
+
* the query cannot be forgotten by a caller the way a state check can.
|
|
6709
|
+
*/
|
|
6710
|
+
|
|
6711
|
+
declare class PasswordResetTokensRepository extends BaseRepository {
|
|
6712
|
+
/**
|
|
6713
|
+
* Create a reset link row.
|
|
6714
|
+
* Write primary.
|
|
6715
|
+
*/
|
|
6716
|
+
create(data: NewPasswordResetToken): Promise<PasswordResetToken>;
|
|
6717
|
+
/**
|
|
6718
|
+
* Create a reset link row that has no token yet.
|
|
6719
|
+
*
|
|
6720
|
+
* The link is minted by the `auth.link-mail` job, not by the request, so the
|
|
6721
|
+
* row starts without a hash and `issue` writes one. Both delivery modes take
|
|
6722
|
+
* this path, which is what keeps "a hash lands only through `issue`" true.
|
|
6723
|
+
*
|
|
6724
|
+
* Write primary.
|
|
6725
|
+
*/
|
|
6726
|
+
createPending(data: {
|
|
6727
|
+
userId: number;
|
|
6728
|
+
email: string;
|
|
6729
|
+
returnPath: string | null;
|
|
6730
|
+
expiresAt: Date;
|
|
6731
|
+
}): Promise<PasswordResetToken>;
|
|
6732
|
+
/**
|
|
6733
|
+
* Write the token hash onto a pending row, but only if the link may still be
|
|
6734
|
+
* delivered: not consumed, not superseded, not completed, not expired.
|
|
6735
|
+
*
|
|
6736
|
+
* One statement rather than a read and a write, so a row superseded by a
|
|
6737
|
+
* newer request between the two never receives a credential — the worker
|
|
6738
|
+
* simply finds nothing to issue and sends no mail.
|
|
6739
|
+
*
|
|
6740
|
+
* @returns the issued row, or null if the row is no longer deliverable
|
|
6741
|
+
*/
|
|
6742
|
+
issue(id: number, tokenHash: string): Promise<PasswordResetToken | null>;
|
|
6743
|
+
/**
|
|
6744
|
+
* Find a link that can still be exchanged for a setup session: unconsumed,
|
|
6745
|
+
* not superseded, not completed, not expired.
|
|
6746
|
+
*
|
|
6747
|
+
* Read replica.
|
|
6748
|
+
*/
|
|
6749
|
+
findLiveByTokenHash(tokenHash: string): Promise<PasswordResetToken | null>;
|
|
6750
|
+
/**
|
|
6751
|
+
* Find a setup session that can still be used to set a password.
|
|
6752
|
+
* Read replica.
|
|
6753
|
+
*/
|
|
6754
|
+
findLiveSetupBySecretHash(setupSecretHash: string): Promise<PasswordResetToken | null>;
|
|
6755
|
+
/**
|
|
6756
|
+
* Consume a link and open a setup session on it, but only if it is still
|
|
6757
|
+
* unconsumed and not superseded.
|
|
6758
|
+
*
|
|
6759
|
+
* @returns the updated row, or null if another request claimed it first
|
|
6760
|
+
*/
|
|
6761
|
+
consume(id: number, setupSecretHash: string, setupExpiresAt: Date): Promise<PasswordResetToken | null>;
|
|
6762
|
+
/**
|
|
6763
|
+
* Mark a setup session as completed, but only if it has not completed
|
|
6764
|
+
* already.
|
|
6765
|
+
*
|
|
6766
|
+
* @returns the updated row, or null if another request completed it first
|
|
6767
|
+
*/
|
|
6768
|
+
complete(id: number): Promise<PasswordResetToken | null>;
|
|
6769
|
+
/**
|
|
6770
|
+
* Supersede every live link for an account, so requesting a new one
|
|
6771
|
+
* invalidates the previous one and any setup session opened from it.
|
|
6772
|
+
*
|
|
6773
|
+
* Write primary.
|
|
6774
|
+
*
|
|
6775
|
+
* @returns number of rows superseded
|
|
6776
|
+
*/
|
|
6777
|
+
supersedeAllLiveByUserId(userId: number): Promise<number>;
|
|
6778
|
+
}
|
|
6779
|
+
declare const passwordResetTokensRepository: PasswordResetTokensRepository;
|
|
6780
|
+
|
|
6781
|
+
/**
|
|
6782
|
+
* Passkeys Repository
|
|
6783
|
+
*
|
|
6784
|
+
* Data access for enrolled WebAuthn credentials. Extends BaseRepository for
|
|
6785
|
+
* transaction-context detection and read/write splitting.
|
|
6786
|
+
*
|
|
6787
|
+
* Two lookups by credential id, deliberately separate. `findLiveByCredentialId`
|
|
6788
|
+
* is the login path and must see only credentials that can still sign.
|
|
6789
|
+
* `existsByCredentialId` is the enrollment path and must see revoked rows too,
|
|
6790
|
+
* because a credential someone cut off stays reserved forever (D5) — one query
|
|
6791
|
+
* serving both would have to pick a filter, and each caller needs the other one.
|
|
6792
|
+
*/
|
|
6793
|
+
|
|
6794
|
+
declare class PasskeysRepository extends BaseRepository {
|
|
6795
|
+
/**
|
|
6796
|
+
* Enroll a credential.
|
|
6797
|
+
* Write primary.
|
|
6798
|
+
*/
|
|
6799
|
+
create(data: NewPasskey): Promise<Passkey>;
|
|
6800
|
+
/**
|
|
6801
|
+
* The live credential with this id, whoever owns it.
|
|
6802
|
+
*
|
|
6803
|
+
* Unfiltered by user on purpose: a discoverable sign-in names no account, so
|
|
6804
|
+
* the credential is what identifies the owner.
|
|
6805
|
+
*
|
|
6806
|
+
* Write primary — the login path reads it inside the transaction that then
|
|
6807
|
+
* moves its counter.
|
|
6808
|
+
*/
|
|
6809
|
+
findLiveByCredentialId(credentialId: string): Promise<Passkey | null>;
|
|
6810
|
+
/**
|
|
6811
|
+
* Whether this credential id is taken on any account, live or revoked.
|
|
6812
|
+
* Write primary — enrollment decides on it inside its own transaction.
|
|
6813
|
+
*/
|
|
6814
|
+
existsByCredentialId(credentialId: string): Promise<boolean>;
|
|
6815
|
+
/**
|
|
6816
|
+
* One owner's live credentials, newest first.
|
|
6817
|
+
* Write primary — both callers (the exclude list, the last-credential guard)
|
|
6818
|
+
* decide something inside a transaction on what they read here.
|
|
6819
|
+
*/
|
|
6820
|
+
listLiveByUserId(userId: number): Promise<Passkey[]>;
|
|
6821
|
+
/**
|
|
6822
|
+
* One live credential owned by this user.
|
|
6823
|
+
* Owner-scoped, so an id belonging to someone else answers null rather than
|
|
6824
|
+
* a row — a management route can only ever say "not yours".
|
|
6825
|
+
*/
|
|
6826
|
+
findLiveByIdAndUserId(id: number, userId: number): Promise<Passkey | null>;
|
|
6827
|
+
/**
|
|
6828
|
+
* Record a successful assertion.
|
|
6829
|
+
* Write primary.
|
|
6830
|
+
*/
|
|
6831
|
+
recordUse(id: number, counter: number): Promise<void>;
|
|
6832
|
+
/**
|
|
6833
|
+
* Rename, but only a credential this user still owns and has not revoked.
|
|
6834
|
+
*
|
|
6835
|
+
* @returns the updated row, or null if it is not theirs or already revoked
|
|
6836
|
+
*/
|
|
6837
|
+
renameByIdAndUserId(id: number, userId: number, label: string): Promise<Passkey | null>;
|
|
6838
|
+
/**
|
|
6839
|
+
* Retire a credential, but only if THIS call is the one that retired it.
|
|
6840
|
+
*
|
|
6841
|
+
* Conditional on `revokedAt IS NULL`, so two concurrent revokes produce one
|
|
6842
|
+
* winner and one 404 rather than both reporting success on a row only one of
|
|
6843
|
+
* them moved.
|
|
6844
|
+
*
|
|
6845
|
+
* @returns the updated row, or null if it is not theirs or already revoked
|
|
6846
|
+
*/
|
|
6847
|
+
revokeByIdAndUserId(id: number, userId: number, reason: string): Promise<Passkey | null>;
|
|
6848
|
+
/**
|
|
6849
|
+
* Delete every passkey of an account (account destruction).
|
|
6850
|
+
* Write primary.
|
|
6851
|
+
*
|
|
6852
|
+
* @returns number of rows deleted
|
|
6853
|
+
*/
|
|
6854
|
+
deleteAllByUserId(userId: number): Promise<number>;
|
|
6855
|
+
}
|
|
6856
|
+
declare const passkeysRepository: PasskeysRepository;
|
|
4311
6857
|
|
|
4312
6858
|
/**
|
|
4313
|
-
*
|
|
6859
|
+
* WebAuthn Challenges Repository
|
|
4314
6860
|
*
|
|
4315
|
-
*
|
|
4316
|
-
* BaseRepository
|
|
6861
|
+
* Data access for the one-time nonces the two WebAuthn ceremonies hand out.
|
|
6862
|
+
* Extends BaseRepository for transaction-context detection and read/write
|
|
6863
|
+
* splitting.
|
|
6864
|
+
*
|
|
6865
|
+
* `consume` is one conditional UPDATE and returns the row only if THIS call was
|
|
6866
|
+
* the one that spent it. Read-then-write would lose that race: two verifies
|
|
6867
|
+
* arriving with the same challenge would both read it as live and both proceed,
|
|
6868
|
+
* which is the replay the challenge exists to stop.
|
|
4317
6869
|
*/
|
|
4318
6870
|
|
|
6871
|
+
declare class WebAuthnChallengesRepository extends BaseRepository {
|
|
6872
|
+
/**
|
|
6873
|
+
* Park a freshly minted challenge.
|
|
6874
|
+
* Write primary.
|
|
6875
|
+
*/
|
|
6876
|
+
create(data: NewWebAuthnChallenge): Promise<WebAuthnChallenge>;
|
|
6877
|
+
/**
|
|
6878
|
+
* Spend a challenge, if it is still live and was minted for this ceremony.
|
|
6879
|
+
*
|
|
6880
|
+
* The whole refusal — unknown, expired, already spent, wrong kind — is this
|
|
6881
|
+
* one statement's WHERE clause, so nothing can read a challenge as usable in
|
|
6882
|
+
* one step and spend it in another. The caller compares `userId` on the row
|
|
6883
|
+
* it gets back; that is the one condition left outside, because a
|
|
6884
|
+
* discoverable login has no user to match and must still spend the row.
|
|
6885
|
+
*
|
|
6886
|
+
* @returns the row this call spent, or null if there was nothing to spend
|
|
6887
|
+
*/
|
|
6888
|
+
consume(challengeHash: string, kind: WebAuthnChallengeKind): Promise<WebAuthnChallenge | null>;
|
|
6889
|
+
/**
|
|
6890
|
+
* Drop challenges nobody can present any more.
|
|
6891
|
+
*
|
|
6892
|
+
* Write primary.
|
|
6893
|
+
*
|
|
6894
|
+
* @returns number of rows deleted
|
|
6895
|
+
*/
|
|
6896
|
+
deleteExpired(): Promise<number>;
|
|
6897
|
+
}
|
|
6898
|
+
declare const webauthnChallengesRepository: WebAuthnChallengesRepository;
|
|
6899
|
+
|
|
4319
6900
|
/**
|
|
4320
|
-
*
|
|
6901
|
+
* Device Authorizations Repository
|
|
4321
6902
|
*
|
|
4322
|
-
*
|
|
4323
|
-
*
|
|
4324
|
-
*
|
|
4325
|
-
*
|
|
6903
|
+
* Data access for device-code login. Extends BaseRepository for transaction
|
|
6904
|
+
* context detection and read/write splitting.
|
|
6905
|
+
*
|
|
6906
|
+
* Every state change here is a conditional UPDATE naming the exact state it is
|
|
6907
|
+
* allowed to move from, returning the row only when THIS call was the one that
|
|
6908
|
+
* moved it. That is what makes the operations safe to race: two polls on one
|
|
6909
|
+
* approved record, or a deny arriving while an approve is in flight, produce one
|
|
6910
|
+
* winner and one refusal instead of both callers acting on a row they each read
|
|
6911
|
+
* as still theirs to move. Read-then-write would lose that.
|
|
6912
|
+
*
|
|
6913
|
+
* Each of those transitions also carries the TTL, as `now()` evaluated by the
|
|
6914
|
+
* database. The service reads a record before it acts on it, and a record can
|
|
6915
|
+
* pass its expiry in the gap between that read and this UPDATE — an approved one
|
|
6916
|
+
* most of all, since the poll that spends it is the one call that registers a
|
|
6917
|
+
* key. Judging the clock only in the read would let that key be registered after
|
|
6918
|
+
* the code was dead.
|
|
4326
6919
|
*/
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
* Target과 Purpose로 유효한 인증 코드 조회
|
|
4330
|
-
* (만료되지 않고, 사용되지 않은 코드)
|
|
4331
|
-
* Read replica 사용
|
|
4332
|
-
*/
|
|
4333
|
-
findValidByTargetAndPurpose(target: string, purpose: VerificationPurpose): Promise<{
|
|
4334
|
-
createdAt: Date;
|
|
4335
|
-
updatedAt: Date;
|
|
4336
|
-
id: number;
|
|
4337
|
-
target: string;
|
|
4338
|
-
targetType: "email" | "phone";
|
|
4339
|
-
code: string;
|
|
4340
|
-
purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
|
|
4341
|
-
expiresAt: Date;
|
|
4342
|
-
usedAt: Date | null;
|
|
4343
|
-
attempts: number;
|
|
4344
|
-
}>;
|
|
6920
|
+
|
|
6921
|
+
declare class DeviceAuthorizationsRepository extends BaseRepository {
|
|
4345
6922
|
/**
|
|
4346
|
-
*
|
|
4347
|
-
*
|
|
6923
|
+
* Insert a pending authorization, unless one of its codes is already taken.
|
|
6924
|
+
*
|
|
6925
|
+
* `onConflictDoNothing` rather than letting the unique index raise: the start
|
|
6926
|
+
* route runs inside a transaction, and a raised unique violation aborts it,
|
|
6927
|
+
* so the retry that a generated-code collision calls for could not run — every
|
|
6928
|
+
* statement after it would fail with "current transaction is aborted" instead.
|
|
6929
|
+
* An empty result is the collision signal, and the caller draws a fresh code.
|
|
6930
|
+
*
|
|
6931
|
+
* Write primary.
|
|
6932
|
+
*
|
|
6933
|
+
* @returns the inserted row, or null if either code collided
|
|
4348
6934
|
*/
|
|
4349
|
-
|
|
4350
|
-
createdAt: Date;
|
|
4351
|
-
updatedAt: Date;
|
|
4352
|
-
id: number;
|
|
4353
|
-
target: string;
|
|
4354
|
-
targetType: "email" | "phone";
|
|
4355
|
-
code: string;
|
|
4356
|
-
purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
|
|
4357
|
-
expiresAt: Date;
|
|
4358
|
-
usedAt: Date | null;
|
|
4359
|
-
attempts: number;
|
|
4360
|
-
}>;
|
|
6935
|
+
create(data: NewDeviceAuthorization): Promise<DeviceAuthorization | null>;
|
|
4361
6936
|
/**
|
|
4362
|
-
*
|
|
4363
|
-
*
|
|
6937
|
+
* Find a record by its normalized user code, in any state.
|
|
6938
|
+
*
|
|
6939
|
+
* Deliberately unfiltered: which refusal a caller is owed — expired, already
|
|
6940
|
+
* handled, unknown — is the service's decision, and a row filtered out here
|
|
6941
|
+
* would be indistinguishable from a code that was never issued.
|
|
6942
|
+
*
|
|
6943
|
+
* Read replica.
|
|
4364
6944
|
*/
|
|
4365
|
-
|
|
4366
|
-
target: string;
|
|
4367
|
-
targetType: "email" | "phone";
|
|
4368
|
-
purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
|
|
4369
|
-
code: string;
|
|
4370
|
-
id: number;
|
|
4371
|
-
createdAt: Date;
|
|
4372
|
-
updatedAt: Date;
|
|
4373
|
-
expiresAt: Date;
|
|
4374
|
-
usedAt: Date | null;
|
|
4375
|
-
attempts: number;
|
|
4376
|
-
}>;
|
|
6945
|
+
findByUserCode(userCode: string): Promise<DeviceAuthorization | null>;
|
|
4377
6946
|
/**
|
|
4378
|
-
*
|
|
4379
|
-
*
|
|
6947
|
+
* Find a record by the hash of a device code, in any state.
|
|
6948
|
+
* Unfiltered for the same reason as `findByUserCode`.
|
|
6949
|
+
*
|
|
6950
|
+
* Read replica.
|
|
4380
6951
|
*/
|
|
4381
|
-
|
|
4382
|
-
createdAt: Date;
|
|
4383
|
-
updatedAt: Date;
|
|
4384
|
-
id: number;
|
|
4385
|
-
target: string;
|
|
4386
|
-
targetType: "email" | "phone";
|
|
4387
|
-
code: string;
|
|
4388
|
-
purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
|
|
4389
|
-
expiresAt: Date;
|
|
4390
|
-
usedAt: Date | null;
|
|
4391
|
-
attempts: number;
|
|
4392
|
-
}>;
|
|
6952
|
+
findByDeviceCodeHash(deviceCodeHash: string): Promise<DeviceAuthorization | null>;
|
|
4393
6953
|
/**
|
|
4394
|
-
*
|
|
4395
|
-
*
|
|
6954
|
+
* Bind the approving user and move the record to `approved`, but only from
|
|
6955
|
+
* `pending`.
|
|
6956
|
+
*
|
|
6957
|
+
* `userId` comes from the approver's authenticated session — never from a
|
|
6958
|
+
* request body — so this is the point where the record gains an owner.
|
|
6959
|
+
*
|
|
6960
|
+
* @returns the updated row, or null if it was no longer pending, or expired
|
|
4396
6961
|
*/
|
|
4397
|
-
|
|
4398
|
-
createdAt: Date;
|
|
4399
|
-
updatedAt: Date;
|
|
4400
|
-
id: number;
|
|
4401
|
-
target: string;
|
|
4402
|
-
targetType: "email" | "phone";
|
|
4403
|
-
code: string;
|
|
4404
|
-
purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
|
|
4405
|
-
expiresAt: Date;
|
|
4406
|
-
usedAt: Date | null;
|
|
4407
|
-
attempts: number;
|
|
4408
|
-
} | null>;
|
|
6962
|
+
approve(id: number, userId: number): Promise<DeviceAuthorization | null>;
|
|
4409
6963
|
/**
|
|
4410
|
-
*
|
|
4411
|
-
*
|
|
6964
|
+
* Move the record to `denied`, but only from `pending`.
|
|
6965
|
+
*
|
|
6966
|
+
* No user is bound: a refusal should leave no record of who was asked.
|
|
6967
|
+
*
|
|
6968
|
+
* @returns the updated row, or null if it was no longer pending, or expired
|
|
4412
6969
|
*/
|
|
4413
|
-
|
|
6970
|
+
deny(id: number): Promise<DeviceAuthorization | null>;
|
|
4414
6971
|
/**
|
|
4415
|
-
*
|
|
6972
|
+
* Refuse every authorization a user still has in flight.
|
|
4416
6973
|
*
|
|
4417
|
-
*
|
|
4418
|
-
*
|
|
4419
|
-
*
|
|
6974
|
+
* This is the device-code half of a global revocation. Revoking
|
|
6975
|
+
* `user_public_keys` alone leaves an approved-but-uncollected record behind,
|
|
6976
|
+
* and the next poll on it registers a brand-new active key — so "sign every
|
|
6977
|
+
* device out" would hand one straight back to whoever was still waiting,
|
|
6978
|
+
* which is exactly the person a revoke-all is usually aimed at.
|
|
6979
|
+
*
|
|
6980
|
+
* `denied` rather than a status of its own: a swept record owes its holder
|
|
6981
|
+
* the answer a refused one owes, on all four operations — the waiting device
|
|
6982
|
+
* is told no and stops polling, and info/approve/deny say the request was
|
|
6983
|
+
* already answered. Adding a fourth live status would add a migration and a
|
|
6984
|
+
* case-table row to record a distinction nothing acts on.
|
|
6985
|
+
*
|
|
6986
|
+
* Expired rows are swept too. A global revoke is not the place to reason
|
|
6987
|
+
* about which dead rows were about to die anyway.
|
|
6988
|
+
*
|
|
6989
|
+
* `pending` alongside `approved` even though a pending row carries no
|
|
6990
|
+
* `userId` today, so only approved rows can match: the state list says which
|
|
6991
|
+
* states this is meant to close, and a later change that binds the user
|
|
6992
|
+
* earlier should not silently reopen the hole.
|
|
6993
|
+
*
|
|
6994
|
+
* @returns the rows this call refused
|
|
4420
6995
|
*/
|
|
4421
|
-
|
|
6996
|
+
denyAllActiveByUserId(userId: number): Promise<DeviceAuthorization[]>;
|
|
4422
6997
|
/**
|
|
4423
|
-
*
|
|
4424
|
-
*
|
|
6998
|
+
* Spend an approved record, but only from `approved`, and address it by the
|
|
6999
|
+
* device code hash the caller actually presented.
|
|
7000
|
+
*
|
|
7001
|
+
* This is the one-shot: the winner of two concurrent polls registers the key,
|
|
7002
|
+
* and the loser sees no approved record and is answered as if the code were
|
|
7003
|
+
* unknown. Matching on the hash rather than on an id read a moment ago keeps
|
|
7004
|
+
* the whole decision in one statement.
|
|
7005
|
+
*
|
|
7006
|
+
* @returns the spent row, or null if it was not approved (any more), or expired
|
|
4425
7007
|
*/
|
|
4426
|
-
|
|
7008
|
+
consumeApproved(deviceCodeHash: string): Promise<DeviceAuthorization | null>;
|
|
4427
7009
|
}
|
|
4428
|
-
declare const
|
|
7010
|
+
declare const deviceAuthorizationsRepository: DeviceAuthorizationsRepository;
|
|
4429
7011
|
|
|
4430
7012
|
/**
|
|
4431
7013
|
* Roles Repository
|
|
@@ -5144,12 +7726,12 @@ declare class InvitationsRepository extends BaseRepository {
|
|
|
5144
7726
|
create(data: NewInvitation): Promise<{
|
|
5145
7727
|
email: string;
|
|
5146
7728
|
metadata: Record<string, any> | null;
|
|
7729
|
+
token: string;
|
|
7730
|
+
status: "pending" | "accepted" | "expired" | "cancelled";
|
|
5147
7731
|
id: number;
|
|
5148
7732
|
roleId: number;
|
|
5149
7733
|
createdAt: Date;
|
|
5150
7734
|
updatedAt: Date;
|
|
5151
|
-
status: "pending" | "accepted" | "expired" | "cancelled";
|
|
5152
|
-
token: string;
|
|
5153
7735
|
invitedBy: number;
|
|
5154
7736
|
expiresAt: Date;
|
|
5155
7737
|
acceptedAt: Date | null;
|
|
@@ -5178,12 +7760,12 @@ declare class InvitationsRepository extends BaseRepository {
|
|
|
5178
7760
|
deleteById(id: number): Promise<{
|
|
5179
7761
|
email: string;
|
|
5180
7762
|
metadata: Record<string, any> | null;
|
|
7763
|
+
token: string;
|
|
7764
|
+
status: "pending" | "accepted" | "expired" | "cancelled";
|
|
5181
7765
|
id: number;
|
|
5182
7766
|
roleId: number;
|
|
5183
7767
|
createdAt: Date;
|
|
5184
7768
|
updatedAt: Date;
|
|
5185
|
-
status: "pending" | "accepted" | "expired" | "cancelled";
|
|
5186
|
-
token: string;
|
|
5187
7769
|
invitedBy: number;
|
|
5188
7770
|
expiresAt: Date;
|
|
5189
7771
|
acceptedAt: Date | null;
|
|
@@ -5572,18 +8154,18 @@ declare class AccountDeletionRequestsRepository extends BaseRepository {
|
|
|
5572
8154
|
* Write primary 사용
|
|
5573
8155
|
*/
|
|
5574
8156
|
create(data: NewAccountDeletionRequest): Promise<{
|
|
8157
|
+
status: "pending" | "cancelled" | "completed";
|
|
5575
8158
|
userId: number | null;
|
|
5576
8159
|
id: number;
|
|
5577
8160
|
createdAt: Date;
|
|
5578
8161
|
updatedAt: Date;
|
|
5579
|
-
status: "pending" | "cancelled" | "completed";
|
|
5580
8162
|
reason: string | null;
|
|
5581
8163
|
purgeScheduledAt: Date;
|
|
5582
8164
|
cancelledAt: Date | null;
|
|
8165
|
+
completedAt: Date | null;
|
|
5583
8166
|
userPublicId: string;
|
|
5584
8167
|
requestedAt: Date;
|
|
5585
8168
|
requestedBy: "admin" | "self";
|
|
5586
|
-
completedAt: Date | null;
|
|
5587
8169
|
purgeStrategy: "anonymize" | "hard-delete" | null;
|
|
5588
8170
|
}>;
|
|
5589
8171
|
/**
|
|
@@ -5668,6 +8250,279 @@ declare class OpsTokensRepository extends BaseRepository {
|
|
|
5668
8250
|
}
|
|
5669
8251
|
declare const opsTokensRepository: OpsTokensRepository;
|
|
5670
8252
|
|
|
8253
|
+
/**
|
|
8254
|
+
* OAuth 2.1 Clients Repository
|
|
8255
|
+
*
|
|
8256
|
+
* Data access for dynamically registered public clients. Extends BaseRepository
|
|
8257
|
+
* for transaction context and read/write splitting like every other auth
|
|
8258
|
+
* repository.
|
|
8259
|
+
*/
|
|
8260
|
+
|
|
8261
|
+
declare class OAuth2ClientsRepository extends BaseRepository {
|
|
8262
|
+
create(data: NewOAuth2Client): Promise<OAuth2Client>;
|
|
8263
|
+
/**
|
|
8264
|
+
* Lookup by the public `client_id` — the authorize and token paths.
|
|
8265
|
+
*
|
|
8266
|
+
* Reads the primary, not the replica, for the reason the ops-token lookup
|
|
8267
|
+
* does: a client registered a moment ago is immediately used, and a replica
|
|
8268
|
+
* read would answer "unknown client" for the length of the replication lag,
|
|
8269
|
+
* at the one moment a CLI is being connected.
|
|
8270
|
+
*/
|
|
8271
|
+
findByClientId(clientId: string): Promise<OAuth2Client | null>;
|
|
8272
|
+
/** Fire-and-forget from the token-issuing path. */
|
|
8273
|
+
updateLastUsedById(id: number): Promise<void>;
|
|
8274
|
+
/**
|
|
8275
|
+
* Register a client unless this address is already at its standing cap.
|
|
8276
|
+
*
|
|
8277
|
+
* The count and the insert are one transaction whose first statement takes
|
|
8278
|
+
* an advisory lock on the address, and all three parts are load-bearing.
|
|
8279
|
+
* Counting on the replica lets replication lag hand out slots that are
|
|
8280
|
+
* already taken. Counting and inserting in two statements lets a second
|
|
8281
|
+
* registration read the same total between them. And a transaction alone
|
|
8282
|
+
* does not close that: under READ COMMITTED each concurrent transaction
|
|
8283
|
+
* counts the rows the others have not committed yet, so twenty requests
|
|
8284
|
+
* arriving together all see zero. The lock is the only part that makes the
|
|
8285
|
+
* cap a number rather than an average, and it is per address, so it costs
|
|
8286
|
+
* nobody else anything.
|
|
8287
|
+
*
|
|
8288
|
+
* @param data - The row to write when there is room for it
|
|
8289
|
+
* @param limit - The address, its cap, and how far back the cap looks
|
|
8290
|
+
* @returns the registered client, or null when the address is at its cap
|
|
8291
|
+
*/
|
|
8292
|
+
createWithinStandingCap(data: NewOAuth2Client, limit: {
|
|
8293
|
+
ip: string;
|
|
8294
|
+
max: number;
|
|
8295
|
+
windowMs: number;
|
|
8296
|
+
}): Promise<OAuth2Client | null>;
|
|
8297
|
+
/**
|
|
8298
|
+
* How many clients this IP registered within the window that nobody has
|
|
8299
|
+
* approved yet.
|
|
8300
|
+
*
|
|
8301
|
+
* The window is what makes this a standing-population cap rather than a
|
|
8302
|
+
* permanent quota. Rows are only freed by the purge job, which sweeps once
|
|
8303
|
+
* a day against a 24-hour threshold — so without a window a row registered
|
|
8304
|
+
* a minute after one sweep holds its slot for nearly two days, and twenty
|
|
8305
|
+
* developers behind one NAT lock their whole office out of registering.
|
|
8306
|
+
* A grant against the client takes it out of the count at any age: a client
|
|
8307
|
+
* somebody approved is not junk.
|
|
8308
|
+
*
|
|
8309
|
+
* Reads the primary, inside the caller's transaction, for the reason
|
|
8310
|
+
* `findByClientId` does — a row written a moment ago is exactly the row this
|
|
8311
|
+
* count exists to see.
|
|
8312
|
+
*/
|
|
8313
|
+
private countRecentUngrantedByIp;
|
|
8314
|
+
/**
|
|
8315
|
+
* Delete clients older than `before` that no user ever approved.
|
|
8316
|
+
*
|
|
8317
|
+
* A registration is an unauthenticated write, so the table fills with rows
|
|
8318
|
+
* from installs that were abandoned at the consent screen. A row with a
|
|
8319
|
+
* grant against it is never touched, whatever its age — that is somebody's
|
|
8320
|
+
* connected CLI.
|
|
8321
|
+
*
|
|
8322
|
+
* @returns how many rows this sweep removed
|
|
8323
|
+
*/
|
|
8324
|
+
deleteStaleUngranted(before: Date): Promise<number>;
|
|
8325
|
+
}
|
|
8326
|
+
declare const oauth2ClientsRepository: OAuth2ClientsRepository;
|
|
8327
|
+
|
|
8328
|
+
/**
|
|
8329
|
+
* OAuth 2.1 Grants Repository
|
|
8330
|
+
*
|
|
8331
|
+
* Data access for the consent records every code and token hangs off.
|
|
8332
|
+
*
|
|
8333
|
+
* Revocation is the operation that matters here, and it is always one statement
|
|
8334
|
+
* per table rather than a read-then-write: a grant being revoked at the same
|
|
8335
|
+
* moment as a refresh rotation must not let the rotation's successor survive,
|
|
8336
|
+
* and `where revoked_at is null` in the UPDATE is what makes the two orderings
|
|
8337
|
+
* produce the same end state.
|
|
8338
|
+
*/
|
|
8339
|
+
|
|
8340
|
+
/** A grant with the client it belongs to — what every list and lookup needs. */
|
|
8341
|
+
interface OAuth2GrantWithClient {
|
|
8342
|
+
grant: OAuth2Grant;
|
|
8343
|
+
client: OAuth2Client;
|
|
8344
|
+
}
|
|
8345
|
+
declare class OAuth2GrantsRepository extends BaseRepository {
|
|
8346
|
+
/**
|
|
8347
|
+
* Record a consent, or refresh the scopes of the one already there.
|
|
8348
|
+
*
|
|
8349
|
+
* Re-consenting is an upsert and not an insert: the unique index on
|
|
8350
|
+
* (client, user, resource) says one consent per triple, and a user who
|
|
8351
|
+
* approves a wider scope set is amending the consent they already gave, not
|
|
8352
|
+
* giving a second one. Revoking the entry they can see must revoke
|
|
8353
|
+
* everything the client holds, which a second row would break.
|
|
8354
|
+
*
|
|
8355
|
+
* `revokedAt: null` in the update is deliberate — approving again after
|
|
8356
|
+
* revoking is how a user reconnects a CLI they cut off.
|
|
8357
|
+
*/
|
|
8358
|
+
upsert(data: NewOAuth2Grant): Promise<OAuth2Grant>;
|
|
8359
|
+
/**
|
|
8360
|
+
* A grant and its client in one read — the shape the token and verification
|
|
8361
|
+
* paths need, since both have to check the client the caller claims and the
|
|
8362
|
+
* grant's revocation at once.
|
|
8363
|
+
*
|
|
8364
|
+
* Read primary: revocation is documented as taking effect immediately.
|
|
8365
|
+
*/
|
|
8366
|
+
findWithClientById(id: number): Promise<OAuth2GrantWithClient | null>;
|
|
8367
|
+
/** What the user's "connected apps" screen lists. Read replica. */
|
|
8368
|
+
listActiveByUserId(userId: number): Promise<OAuth2GrantWithClient[]>;
|
|
8369
|
+
/**
|
|
8370
|
+
* Revoke one live grant belonging to one user.
|
|
8371
|
+
*
|
|
8372
|
+
* The user id is part of the condition rather than checked beforehand: the
|
|
8373
|
+
* id in the URL comes from a caller, and a grant that is not theirs must not
|
|
8374
|
+
* be revocable by guessing a number. A miss is indistinguishable from an id
|
|
8375
|
+
* that does not exist, which is the answer the route gives.
|
|
8376
|
+
*
|
|
8377
|
+
* @returns the revoked row, or null if there was no live grant of that id
|
|
8378
|
+
* for that user
|
|
8379
|
+
*/
|
|
8380
|
+
revokeByIdForUser(id: number, userId: number): Promise<OAuth2Grant | null>;
|
|
8381
|
+
/** Revoke one grant by id, whoever it belongs to — the replay detections. */
|
|
8382
|
+
revokeById(id: number): Promise<OAuth2Grant | null>;
|
|
8383
|
+
/**
|
|
8384
|
+
* Revoke every live grant a user has — the authorization-server half of a
|
|
8385
|
+
* global revocation, beside `denyAllActiveByUserId`.
|
|
8386
|
+
*
|
|
8387
|
+
* @returns the grant ids this call revoked
|
|
8388
|
+
*/
|
|
8389
|
+
revokeAllActiveByUserId(userId: number): Promise<number[]>;
|
|
8390
|
+
/**
|
|
8391
|
+
* Revoke every live token under the given grants.
|
|
8392
|
+
*
|
|
8393
|
+
* Verification already refuses a token whose grant is revoked, so this is
|
|
8394
|
+
* belt and braces — but the belt is what a `SELECT` against this table shows
|
|
8395
|
+
* an operator, and a live-looking row under a dead grant reads as a hole.
|
|
8396
|
+
*
|
|
8397
|
+
* @returns how many tokens this call revoked
|
|
8398
|
+
*/
|
|
8399
|
+
revokeTokensOfGrants(grantIds: number[]): Promise<number>;
|
|
8400
|
+
}
|
|
8401
|
+
declare const oauth2GrantsRepository: OAuth2GrantsRepository;
|
|
8402
|
+
|
|
8403
|
+
/**
|
|
8404
|
+
* OAuth 2.1 Authorization Codes Repository
|
|
8405
|
+
*
|
|
8406
|
+
* Two operations and one of them is the whole point. `consume` is a conditional
|
|
8407
|
+
* UPDATE that names unspent and not-expired and writes `used_at` in the same
|
|
8408
|
+
* statement that reads the row, so of two token requests racing on one code
|
|
8409
|
+
* exactly one comes back with a row. Read-then-write would hand both of them
|
|
8410
|
+
* tokens.
|
|
8411
|
+
*
|
|
8412
|
+
* The TTL travels in that statement as `now()` evaluated by the database, for
|
|
8413
|
+
* the reason the device-code transitions carry theirs: the row is judged by the
|
|
8414
|
+
* same clock that stores it, so an app server whose clock drifted cannot extend
|
|
8415
|
+
* a code's life.
|
|
8416
|
+
*
|
|
8417
|
+
* `findByCodeHash` exists only to explain a miss. A code that is simply unknown
|
|
8418
|
+
* and one that was already spent get the same answer at the endpoint —
|
|
8419
|
+
* `invalid_grant` either way — but the spent one also revokes the grant, and
|
|
8420
|
+
* that decision needs the row.
|
|
8421
|
+
*/
|
|
8422
|
+
|
|
8423
|
+
declare class OAuth2AuthorizationCodesRepository extends BaseRepository {
|
|
8424
|
+
create(data: NewOAuth2AuthorizationCode): Promise<OAuth2AuthorizationCode>;
|
|
8425
|
+
/**
|
|
8426
|
+
* Spend a code, addressed by the hash the caller actually presented.
|
|
8427
|
+
*
|
|
8428
|
+
* @returns the row this call spent, or null when it was unknown, already
|
|
8429
|
+
* spent, or past its 60 seconds
|
|
8430
|
+
*/
|
|
8431
|
+
consume(codeHash: string): Promise<OAuth2AuthorizationCode | null>;
|
|
8432
|
+
/**
|
|
8433
|
+
* The row behind a miss, in any state. Deliberately unfiltered — which
|
|
8434
|
+
* refusal is owed is the service's decision, and a row filtered out here
|
|
8435
|
+
* would be indistinguishable from a code that never existed.
|
|
8436
|
+
*/
|
|
8437
|
+
findByCodeHash(codeHash: string): Promise<OAuth2AuthorizationCode | null>;
|
|
8438
|
+
}
|
|
8439
|
+
declare const oauth2AuthorizationCodesRepository: OAuth2AuthorizationCodesRepository;
|
|
8440
|
+
|
|
8441
|
+
/**
|
|
8442
|
+
* OAuth 2.1 Tokens Repository
|
|
8443
|
+
*
|
|
8444
|
+
* Access and refresh rows, and the rotation that turns one refresh into the
|
|
8445
|
+
* next.
|
|
8446
|
+
*
|
|
8447
|
+
* `rotate` is the code-consuming statement again, in the other table: mark the
|
|
8448
|
+
* presented refresh `replaced_at` only if it is live, and return it only when
|
|
8449
|
+
* THIS call was the one that marked it. Two clients refreshing the same token at
|
|
8450
|
+
* once therefore produce one rotation and one refusal, instead of two token
|
|
8451
|
+
* pairs off one refresh.
|
|
8452
|
+
*
|
|
8453
|
+
* Nothing is deleted. A rotated refresh keeps its row so that presenting it
|
|
8454
|
+
* again is distinguishable from presenting a token that never existed, which is
|
|
8455
|
+
* the entire reuse detection.
|
|
8456
|
+
*/
|
|
8457
|
+
|
|
8458
|
+
declare class OAuth2TokensRepository extends BaseRepository {
|
|
8459
|
+
create(data: NewOAuth2Token): Promise<OAuth2Token>;
|
|
8460
|
+
/**
|
|
8461
|
+
* Lookup by the token's hash — the verification and refresh paths.
|
|
8462
|
+
*
|
|
8463
|
+
* Primary, not replica: revocation is a button the user presses and is
|
|
8464
|
+
* documented as taking effect immediately, so a replica read would keep
|
|
8465
|
+
* authenticating a revoked token for the length of the replication lag.
|
|
8466
|
+
* Unfiltered, so the service can tell revoked from expired from unknown.
|
|
8467
|
+
*/
|
|
8468
|
+
findByTokenHash(tokenHash: string): Promise<OAuth2Token | null>;
|
|
8469
|
+
/**
|
|
8470
|
+
* Mark a live refresh token as replaced, addressed by its hash.
|
|
8471
|
+
*
|
|
8472
|
+
* @returns the row this call replaced, or null when it was unknown, already
|
|
8473
|
+
* rotated, revoked, or expired
|
|
8474
|
+
*/
|
|
8475
|
+
rotate(tokenHash: string): Promise<OAuth2Token | null>;
|
|
8476
|
+
/**
|
|
8477
|
+
* Revoke a live token by hash — RFC 7009.
|
|
8478
|
+
*
|
|
8479
|
+
* @returns the revoked row, or null when there was nothing live to revoke.
|
|
8480
|
+
* The endpoint answers 200 either way; the caller learns nothing
|
|
8481
|
+
* about whether the value it presented ever existed.
|
|
8482
|
+
*/
|
|
8483
|
+
revokeByTokenHash(tokenHash: string): Promise<OAuth2Token | null>;
|
|
8484
|
+
/** Fire-and-forget from the verification path, as ops tokens do. */
|
|
8485
|
+
updateLastUsedById(id: number): Promise<void>;
|
|
8486
|
+
}
|
|
8487
|
+
declare const oauth2TokensRepository: OAuth2TokensRepository;
|
|
8488
|
+
|
|
8489
|
+
/**
|
|
8490
|
+
* @spfn/auth - Email address normalization
|
|
8491
|
+
*
|
|
8492
|
+
* One canonical form for an address, applied on the way in AND on the way out,
|
|
8493
|
+
* so `Foo@Example.com` and `foo@example.com` are the same account everywhere.
|
|
8494
|
+
*
|
|
8495
|
+
* Both sides have to agree or normalization makes things worse rather than
|
|
8496
|
+
* better: normalizing only the lookup means an address stored in mixed case can
|
|
8497
|
+
* no longer be found, and normalizing only the write means an old row stays
|
|
8498
|
+
* unreachable. `UsersRepository` therefore applies this to every read and every
|
|
8499
|
+
* write of `users.email`, which is what keeps a call site from forgetting.
|
|
8500
|
+
*/
|
|
8501
|
+
/**
|
|
8502
|
+
* Trim and lower-case an address.
|
|
8503
|
+
*
|
|
8504
|
+
* Nothing else. Two normalizations that look tempting are deliberately absent:
|
|
8505
|
+
* stripping dots and cutting at `+` are Gmail delivery rules, not internet ones,
|
|
8506
|
+
* and applying them would merge addresses that other providers treat as
|
|
8507
|
+
* genuinely different people.
|
|
8508
|
+
*
|
|
8509
|
+
* The local part is technically case-sensitive per RFC 5321, but no mail
|
|
8510
|
+
* provider in practice treats it that way, while users routinely retype their
|
|
8511
|
+
* address with different capitalization. Matching the practice avoids duplicate
|
|
8512
|
+
* accounts; honouring the RFC creates them.
|
|
8513
|
+
*
|
|
8514
|
+
* @param email - Address as supplied
|
|
8515
|
+
* @returns The canonical form used for storage and lookup
|
|
8516
|
+
*/
|
|
8517
|
+
declare function normalizeEmail(email: string): string;
|
|
8518
|
+
/**
|
|
8519
|
+
* Normalize when there is an address, pass null/undefined through untouched.
|
|
8520
|
+
*
|
|
8521
|
+
* Accounts registered by phone have no email, and `null` is meaningfully
|
|
8522
|
+
* different from `''` in that column.
|
|
8523
|
+
*/
|
|
8524
|
+
declare function normalizeOptionalEmail<T extends string | null | undefined>(email: T): T;
|
|
8525
|
+
|
|
5671
8526
|
/**
|
|
5672
8527
|
* @spfn/auth - Password Helpers
|
|
5673
8528
|
*
|
|
@@ -5864,6 +8719,23 @@ declare function decodeToken(token: string): TokenPayload | null;
|
|
|
5864
8719
|
* ```
|
|
5865
8720
|
*/
|
|
5866
8721
|
declare function verifyKeyFingerprint(publicKeyB64: string, expectedFingerprint: string): boolean;
|
|
8722
|
+
/**
|
|
8723
|
+
* Refuses a public key whose SPKI type is not the one its declared algorithm
|
|
8724
|
+
* needs, before the key is stored.
|
|
8725
|
+
*
|
|
8726
|
+
* The algorithm column is what proof verification later reads, and nothing
|
|
8727
|
+
* re-derives it from the key material — so an EC key parked as RS256 is
|
|
8728
|
+
* accepted at enrollment and fails on every request afterwards, with the device
|
|
8729
|
+
* already believing it is enrolled. Refusing at registration is what keeps that
|
|
8730
|
+
* mismatch from ever being written.
|
|
8731
|
+
*
|
|
8732
|
+
* Bytes that are no SPKI public key at all are refused the same way: the parser
|
|
8733
|
+
* throws on them, and an unreadable key is the same defect as a mismatched one.
|
|
8734
|
+
* Neither is a server fault, so both answer 400 rather than falling out as a 500.
|
|
8735
|
+
*
|
|
8736
|
+
* @throws KeyAlgorithmMismatchError when the key is not the algorithm's type.
|
|
8737
|
+
*/
|
|
8738
|
+
declare function assertKeyMatchesAlgorithm(publicKeyB64: string, algorithm: KeyAlgorithmType): void;
|
|
5867
8739
|
|
|
5868
8740
|
/**
|
|
5869
8741
|
* @spfn/auth - Permission Middleware
|
|
@@ -6061,6 +8933,86 @@ declare const opsTokenAuth: _spfn_core_route.NamedMiddleware<"opsTokenAuth">;
|
|
|
6061
8933
|
*/
|
|
6062
8934
|
declare const requireOpsScope: _spfn_core_route.NamedMiddlewareFactory<"opsScope", string[]>;
|
|
6063
8935
|
|
|
8936
|
+
/**
|
|
8937
|
+
* @spfn/auth - One route, two credentials
|
|
8938
|
+
*
|
|
8939
|
+
* An operator action is scripted today (the CLI, holding an ops token) and
|
|
8940
|
+
* driven from an admin console tomorrow (a browser, holding a user session).
|
|
8941
|
+
* That is one route with two admissible credentials, and neither existing
|
|
8942
|
+
* middleware admits both: `authenticate` refuses an `spfn_ops_` bearer before
|
|
8943
|
+
* any scope guard runs (#79 — the user path never admits a machine
|
|
8944
|
+
* credential), and `opsTokenAuth` admits nothing else. `opsOrUser` composes
|
|
8945
|
+
* the two existing pairs behind one middleware so an application does not
|
|
8946
|
+
* hand-write the branch.
|
|
8947
|
+
*
|
|
8948
|
+
* The rules it keeps:
|
|
8949
|
+
*
|
|
8950
|
+
* - **The branch is chosen by credential shape, never by caller choice.** The
|
|
8951
|
+
* raw `Authorization` bearer is tested with `isOpsToken`; a match takes the
|
|
8952
|
+
* ops branch, everything else (user JWT, another machine namespace,
|
|
8953
|
+
* malformed, absent) takes the user branch. A caller cannot select the
|
|
8954
|
+
* weaker check by asking for it, because nothing in the request selects a
|
|
8955
|
+
* branch except the credential it presents.
|
|
8956
|
+
* - **`roles` and `permissions` are AND, roles first.** Two lists only ever
|
|
8957
|
+
* narrow. An OR would mean one added role voids the whole permission list,
|
|
8958
|
+
* which is the opposite of what a reader of the two lists expects. Roles run
|
|
8959
|
+
* first because the role is already on the auth context while permissions
|
|
8960
|
+
* cost a lookup — and so a caller with the wrong role is refused for the
|
|
8961
|
+
* wrong role.
|
|
8962
|
+
* - **No implicit admin bypass.** Permissions match by name only, and the ops
|
|
8963
|
+
* branch has no role concept, so neither branch has a principal that passes
|
|
8964
|
+
* by virtue of being an administrator.
|
|
8965
|
+
* - **A refusal is the selected branch's own refusal.** No error class and no
|
|
8966
|
+
* wire message is introduced here; whichever guard refused answers exactly
|
|
8967
|
+
* what it answers on a single-credential route.
|
|
8968
|
+
* - **It is a `NamedMiddleware`, not a `defineMiddleware` factory.** A factory
|
|
8969
|
+
* produces a bare handler, and `register-routes` reads `skips` only off a
|
|
8970
|
+
* `NamedMiddleware` object — so a factory form would put `.skip(['auth'])`
|
|
8971
|
+
* back on every route, which is the hand-written workaround this exists to
|
|
8972
|
+
* remove. Carrying `skips: ['auth']` makes the global `auth` middleware
|
|
8973
|
+
* auto-skip, exactly as `optionalAuth` and `opsTokenAuth` do.
|
|
8974
|
+
*
|
|
8975
|
+
* The backend never reads cookies. A browser session reaches here as a Bearer
|
|
8976
|
+
* token because `@spfn/auth/nextjs/api` forwards it as one; a request carrying
|
|
8977
|
+
* only a `Cookie` header is an unauthenticated request on this route.
|
|
8978
|
+
*
|
|
8979
|
+
* @example
|
|
8980
|
+
* ```typescript
|
|
8981
|
+
* import { opsOrUser } from '@spfn/auth/server';
|
|
8982
|
+
*
|
|
8983
|
+
* export const exportSignups = route.get('/admin/signups/export')
|
|
8984
|
+
* .use([opsOrUser({ opsScopes: ['waitlist:read'], permissions: ['admin.waitlist'] })])
|
|
8985
|
+
* .handler(async (c) =>
|
|
8986
|
+
* {
|
|
8987
|
+
* // exactly one of these is set
|
|
8988
|
+
* const ops = getOpsToken(c.raw);
|
|
8989
|
+
* const user = getAuth(c.raw);
|
|
8990
|
+
* });
|
|
8991
|
+
* ```
|
|
8992
|
+
*/
|
|
8993
|
+
|
|
8994
|
+
/** What a route admits, on each of the two branches. */
|
|
8995
|
+
interface OpsOrUserConfig {
|
|
8996
|
+
/** scopes an ops token must carry (all of them; '*' grants all, as requireOpsScope does) */
|
|
8997
|
+
opsScopes: string[];
|
|
8998
|
+
/** the session user's role must be one of these */
|
|
8999
|
+
roles?: string[];
|
|
9000
|
+
/** the session user must hold every one of these */
|
|
9001
|
+
permissions?: string[];
|
|
9002
|
+
}
|
|
9003
|
+
/**
|
|
9004
|
+
* A middleware admitting either an ops token or a user session on one route.
|
|
9005
|
+
*
|
|
9006
|
+
* Validation is definition-time and fails closed: a route that names no ops
|
|
9007
|
+
* scope, or no session guard at all, would admit one credential unchecked, so
|
|
9008
|
+
* it is a boot-time error rather than a request-time surprise — the same
|
|
9009
|
+
* posture `registerMachineVerifier` takes.
|
|
9010
|
+
*
|
|
9011
|
+
* @throws Error when `opsScopes` is empty, or when neither `roles` nor
|
|
9012
|
+
* `permissions` names anything.
|
|
9013
|
+
*/
|
|
9014
|
+
declare function opsOrUser(config: OpsOrUserConfig): NamedMiddleware<'opsOrUser'>;
|
|
9015
|
+
|
|
6064
9016
|
/**
|
|
6065
9017
|
* Auth Context Helpers
|
|
6066
9018
|
*
|
|
@@ -6129,15 +9081,15 @@ declare function getUser(c: Context | {
|
|
|
6129
9081
|
}): {
|
|
6130
9082
|
email: string | null;
|
|
6131
9083
|
phone: string | null;
|
|
6132
|
-
|
|
9084
|
+
status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
|
|
6133
9085
|
publicId: string;
|
|
9086
|
+
passwordChangeRequired: boolean;
|
|
9087
|
+
id: number;
|
|
6134
9088
|
username: string | null;
|
|
6135
9089
|
passwordHash: string | null;
|
|
6136
|
-
passwordChangeRequired: boolean;
|
|
6137
9090
|
roleId: number;
|
|
6138
9091
|
createdAt: Date;
|
|
6139
9092
|
updatedAt: Date;
|
|
6140
|
-
status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
|
|
6141
9093
|
emailVerifiedAt: Date | null;
|
|
6142
9094
|
phoneVerifiedAt: Date | null;
|
|
6143
9095
|
lastLoginAt: Date | null;
|
|
@@ -6206,160 +9158,311 @@ declare function getKeyId(c: Context | {
|
|
|
6206
9158
|
}): string;
|
|
6207
9159
|
|
|
6208
9160
|
/**
|
|
6209
|
-
* @spfn/auth -
|
|
9161
|
+
* @spfn/auth - CSRF token derivation
|
|
6210
9162
|
*
|
|
6211
|
-
*
|
|
9163
|
+
* The token is an HMAC of the session's key id under a subkey derived from the
|
|
9164
|
+
* session secret. Two properties follow from that shape:
|
|
9165
|
+
*
|
|
9166
|
+
* - The proxy recomputes it from the session it just unsealed, so a value an
|
|
9167
|
+
* attacker planted in the readable cookie (sibling-subdomain cookie tossing)
|
|
9168
|
+
* never verifies. Nothing here compares a cookie against a header.
|
|
9169
|
+
* - It is bound to the key id, so rotating the session key invalidates it.
|
|
9170
|
+
*
|
|
9171
|
+
* No new secret: the subkey is a labelled HMAC of SPFN_AUTH_SESSION_SECRET, so
|
|
9172
|
+
* the key that encrypts sessions is never used verbatim as the token key.
|
|
6212
9173
|
*/
|
|
9174
|
+
/** Header the readable CSRF cookie is mirrored into by the client. */
|
|
9175
|
+
declare const CSRF_HEADER = "x-spfn-csrf";
|
|
9176
|
+
/**
|
|
9177
|
+
* Derive the CSRF token for a session key id.
|
|
9178
|
+
*
|
|
9179
|
+
* @param keyId - Session key id (`SessionData.keyId`)
|
|
9180
|
+
* @returns 64-char hex token — safe to put in a readable cookie, it reveals
|
|
9181
|
+
* neither the secret nor the key id
|
|
9182
|
+
*/
|
|
9183
|
+
declare function deriveCsrfToken(keyId: string): Promise<string>;
|
|
9184
|
+
/**
|
|
9185
|
+
* Whether a presented header value carries the expected token.
|
|
9186
|
+
*
|
|
9187
|
+
* The header may carry several comma-separated candidates — a browser sees every
|
|
9188
|
+
* `spfn_csrf*` cookie set on the host and cannot tell which dev instance owns
|
|
9189
|
+
* which, nor which of two same-named cookies a sibling subdomain tossed in.
|
|
9190
|
+
*
|
|
9191
|
+
* Every candidate the client is allowed to send is checked — the cap here is the
|
|
9192
|
+
* one it selects against — so a genuine value is never evicted by tossed ones
|
|
9193
|
+
* that happen to sort ahead of it. A header longer than that can only come from a
|
|
9194
|
+
* client that ignored the shared bound, and its surplus is dropped. Accepting any
|
|
9195
|
+
* match is no weaker than accepting one: a candidate the attacker chose still has
|
|
9196
|
+
* to equal a value recomputed from the session.
|
|
9197
|
+
*/
|
|
9198
|
+
declare function matchesCsrfToken(expected: string, presented: string | null | undefined): boolean;
|
|
6213
9199
|
|
|
6214
9200
|
/**
|
|
6215
|
-
*
|
|
9201
|
+
* OAuth 2.1 Authorization Server Configuration
|
|
6216
9202
|
*
|
|
6217
|
-
*
|
|
6218
|
-
*
|
|
9203
|
+
* Singleton config, mirroring `lib/device-auth-config.ts`: a mutable
|
|
9204
|
+
* module-level value, set once from `createAuthLifecycle()` and read at request
|
|
9205
|
+
* time.
|
|
9206
|
+
*
|
|
9207
|
+
* Unlike the device-auth block this one has no defaults to fall back on, and
|
|
9208
|
+
* that is the feature: an application that does not pass `authorizationServer`
|
|
9209
|
+
* is not running an authorization server, and every route under
|
|
9210
|
+
* `/_auth/oauth2/*` answers 404 for it. Nothing here can be derived — the scope
|
|
9211
|
+
* names are the application's vocabulary, and inventing a set would publish
|
|
9212
|
+
* scopes in the metadata document that nothing honours.
|
|
6219
9213
|
*/
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
9214
|
+
/**
|
|
9215
|
+
* Everything the authorization server needs, resolved.
|
|
9216
|
+
*
|
|
9217
|
+
* `issuer` and `authorizeUrl` are absolute; the defaults are derived here, once,
|
|
9218
|
+
* rather than at each use — the metadata document publishes them and a client
|
|
9219
|
+
* that read one value must not meet another on the next request.
|
|
9220
|
+
*/
|
|
9221
|
+
interface AuthorizationServerConfig {
|
|
9222
|
+
/** Origin the tokens are issued by, no path. `SPFN_API_URL` by default. */
|
|
9223
|
+
issuer: string;
|
|
9224
|
+
/**
|
|
9225
|
+
* Where `issuer` came from — the option name or the variable name — so the
|
|
9226
|
+
* boot refusal can tell an operator which one to edit.
|
|
9227
|
+
*/
|
|
9228
|
+
issuerSource: string;
|
|
9229
|
+
/** Consent screen, on the web app: `${appUrl}/oauth/authorize` by default. */
|
|
9230
|
+
authorizeUrl: string;
|
|
9231
|
+
/** Scope name to the one-line description the consent screen shows. */
|
|
9232
|
+
scopes: Record<string, string>;
|
|
9233
|
+
/** What an authorize request with no `scope` asks for. All of them by default. */
|
|
9234
|
+
defaultScopes: string[];
|
|
9235
|
+
/** `https://` origins a client may register a redirect URI on. Loopback needs no entry. */
|
|
9236
|
+
allowedRedirectOrigins: string[];
|
|
9237
|
+
accessTokenTtlMs: number;
|
|
9238
|
+
refreshTokenTtlMs: number;
|
|
9239
|
+
codeTtlMs: number;
|
|
9240
|
+
}
|
|
9241
|
+
/** Options as an application writes them. Everything but `scopes` has a default. */
|
|
9242
|
+
interface AuthorizationServerOptions {
|
|
9243
|
+
issuer?: string;
|
|
9244
|
+
authorizeUrl?: string;
|
|
9245
|
+
scopes: Record<string, string>;
|
|
9246
|
+
defaultScopes?: string[];
|
|
9247
|
+
allowedRedirectOrigins?: string[];
|
|
9248
|
+
accessTokenTtlMs?: number;
|
|
9249
|
+
refreshTokenTtlMs?: number;
|
|
9250
|
+
codeTtlMs?: number;
|
|
9251
|
+
}
|
|
9252
|
+
/** Eight hours. Long enough for a working session, short enough to matter. */
|
|
9253
|
+
declare const DEFAULT_ACCESS_TOKEN_TTL_MS: number;
|
|
9254
|
+
/** Thirty days. The CLI is expected to be reconnected about that often. */
|
|
9255
|
+
declare const DEFAULT_REFRESH_TOKEN_TTL_MS: number;
|
|
9256
|
+
/** Sixty seconds — the code travels from a browser redirect to a local listener. */
|
|
9257
|
+
declare const DEFAULT_CODE_TTL_MS: number;
|
|
9258
|
+
/** Path the consent screen is served at on the web app. */
|
|
9259
|
+
declare const AUTHORIZE_PATH = "/oauth/authorize";
|
|
9260
|
+
/** The API origin every default is derived from, and the boot check's subject. */
|
|
9261
|
+
declare function resolveIssuerSource(env?: Record<string, string | undefined>): {
|
|
9262
|
+
value: string | undefined;
|
|
9263
|
+
variable: string;
|
|
6229
9264
|
};
|
|
6230
9265
|
/**
|
|
6231
|
-
*
|
|
9266
|
+
* Resolve the authorization server config, or clear it when the application
|
|
9267
|
+
* passed none. Called synchronously from `createAuthLifecycle()` for the reason
|
|
9268
|
+
* `configureDeviceAuth` is: nothing may serve a request under half a config.
|
|
6232
9269
|
*
|
|
6233
|
-
*
|
|
6234
|
-
*
|
|
6235
|
-
*
|
|
6236
|
-
*
|
|
9270
|
+
* The issuer is reduced to the form this server publishes here, where every
|
|
9271
|
+
* other default is resolved, so the config is publishable in exactly one state
|
|
9272
|
+
* whether or not a lifecycle hook has run. It is not *validated* here: a value
|
|
9273
|
+
* this cannot reduce is stored as the operator wrote it and refused by
|
|
9274
|
+
* `assertAuthorizationServerIssuer` from `afterInfrastructure`, where a throw
|
|
9275
|
+
* exits the process instead of leaving a server listening without routes. See
|
|
9276
|
+
* `lifecycle.ts`.
|
|
6237
9277
|
*/
|
|
6238
|
-
declare function
|
|
6239
|
-
name: string;
|
|
6240
|
-
value: string;
|
|
6241
|
-
}[];
|
|
9278
|
+
declare function configureAuthorizationServer(options?: AuthorizationServerOptions, env?: Record<string, string | undefined>): void;
|
|
6242
9279
|
/**
|
|
6243
|
-
*
|
|
9280
|
+
* The resolved config, or null when this application runs no authorization
|
|
9281
|
+
* server. Every route under `/_auth/oauth2/*` reads it and answers 404 on null.
|
|
9282
|
+
*/
|
|
9283
|
+
declare function getAuthorizationServerConfig(): AuthorizationServerConfig | null;
|
|
9284
|
+
/**
|
|
9285
|
+
* `localhost`, `127.0.0.1` and `[::1]` — the hosts a browser treats as a secure
|
|
9286
|
+
* context over plain http, which is why the issuer may be `http://` on them and
|
|
9287
|
+
* nowhere else. `URL.hostname` normalises every IPv6 spelling to `[::1]`.
|
|
9288
|
+
*/
|
|
9289
|
+
declare function isLoopbackHostname(hostname: string): boolean;
|
|
9290
|
+
/**
|
|
9291
|
+
* Refuse boot on an issuer no client could use.
|
|
6244
9292
|
*
|
|
6245
|
-
*
|
|
9293
|
+
* `.well-known/*` lives at an origin's root, so an issuer carrying a path has no
|
|
9294
|
+
* place to publish its metadata and every discovery request 404s. An issuer that
|
|
9295
|
+
* is neither https nor loopback http means tokens crossing the network in the
|
|
9296
|
+
* clear. Both are configuration drift between environments, so the deploy that
|
|
9297
|
+
* introduces them is where they have to surface — the posture, the place and the
|
|
9298
|
+
* voice of `assertOAuthRedirectUris`, one door down.
|
|
6246
9299
|
*
|
|
6247
|
-
*
|
|
6248
|
-
*
|
|
6249
|
-
*
|
|
6250
|
-
*
|
|
6251
|
-
*
|
|
9300
|
+
* This only asserts. `configureAuthorizationServer` reduced the value it reads
|
|
9301
|
+
* if it could, so anything reaching here that is not an origin is one the
|
|
9302
|
+
* reduction deliberately left alone.
|
|
9303
|
+
*
|
|
9304
|
+
* Silent when no authorization server is configured: an application that does
|
|
9305
|
+
* not run one must boot exactly as it did before this feature existed.
|
|
9306
|
+
*
|
|
9307
|
+
* @throws When the resolved issuer cannot be honoured. The message names the
|
|
9308
|
+
* variable or the option the value came from.
|
|
6252
9309
|
*/
|
|
6253
|
-
declare function
|
|
9310
|
+
declare function assertAuthorizationServerIssuer(): void;
|
|
9311
|
+
|
|
6254
9312
|
/**
|
|
6255
|
-
*
|
|
9313
|
+
* OAuth 2.1 redirect URI rules
|
|
6256
9314
|
*
|
|
6257
|
-
*
|
|
6258
|
-
*
|
|
6259
|
-
*
|
|
9315
|
+
* Two jobs, and they are not the same job. Registration decides whether a URI
|
|
9316
|
+
* may be written down at all; matching decides whether the URI on an authorize
|
|
9317
|
+
* request is one that was. Registration is where policy lives (no plain http off
|
|
9318
|
+
* loopback, https only on origins the application allows, no fragment ever), and
|
|
9319
|
+
* matching is deliberately dumb: exact, on everything except a loopback port.
|
|
9320
|
+
*
|
|
9321
|
+
* The loopback exception is the whole reason a CLI can use this flow. `claude`
|
|
9322
|
+
* opens a listener on whatever port the OS hands it and cannot know that port
|
|
9323
|
+
* when it registers, so RFC 8252 §7.3 lets the port vary — and nothing else.
|
|
9324
|
+
* `localhost`, `127.0.0.1` and `[::1]` are three different registrations that do
|
|
9325
|
+
* not stand in for one another: they resolve differently on a machine with a
|
|
9326
|
+
* split-horizon resolver, and a client that registered one and is answered on
|
|
9327
|
+
* another is a client something redirected.
|
|
9328
|
+
*
|
|
9329
|
+
* IPv6 arrives in as many spellings as there are ways to write zeroes, so both
|
|
9330
|
+
* sides are read through `new URL(...).hostname`, which answers `[::1]` for
|
|
9331
|
+
* every one of them. The registered value is stored verbatim — the registration
|
|
9332
|
+
* response echoes it back and must not answer a client with a URI it did not
|
|
9333
|
+
* write — so the normalisation happens here, on both sides, at every comparison.
|
|
6260
9334
|
*/
|
|
6261
|
-
|
|
9335
|
+
/** Why a redirect URI cannot be registered, in RFC 7591 §3.2.2 terms. */
|
|
9336
|
+
type RedirectUriRefusal = 'invalid_redirect_uri';
|
|
6262
9337
|
/**
|
|
6263
|
-
*
|
|
9338
|
+
* Whether a URI may be registered, given the https origins this application
|
|
9339
|
+
* allows.
|
|
6264
9340
|
*
|
|
6265
|
-
*
|
|
6266
|
-
*
|
|
9341
|
+
* @param uri - The value exactly as the client wrote it
|
|
9342
|
+
* @param allowedRedirectOrigins - `https://` origins from the lifecycle config
|
|
9343
|
+
* @returns null when it may; the refusal detail when it may not
|
|
6267
9344
|
*/
|
|
6268
|
-
|
|
6269
|
-
channel: RegisterChannel;
|
|
6270
|
-
/** Social provider — only set when channel is 'oauth' */
|
|
6271
|
-
provider?: SocialProvider;
|
|
6272
|
-
email?: string;
|
|
6273
|
-
/**
|
|
6274
|
-
* Whether the email is verified — only set when channel is 'oauth'.
|
|
6275
|
-
* OAuth providers may report an unverified (spoofable) email; the created
|
|
6276
|
-
* account stores it as null in that case, so email-based policies must
|
|
6277
|
-
* check this flag. credentials/invitation emails are already verified.
|
|
6278
|
-
*/
|
|
6279
|
-
emailVerified?: boolean;
|
|
6280
|
-
phone?: string;
|
|
6281
|
-
/** App-supplied registration metadata (register params / OAuth start params / invitation) */
|
|
6282
|
-
metadata?: Record<string, unknown>;
|
|
6283
|
-
}
|
|
9345
|
+
declare function refuseRedirectUriRegistration(uri: string, allowedRedirectOrigins: string[]): string | null;
|
|
6284
9346
|
/**
|
|
6285
|
-
*
|
|
9347
|
+
* Whether a presented redirect URI is one of the registered ones.
|
|
9348
|
+
*
|
|
9349
|
+
* Host, path and query must be identical; the port may differ only when both
|
|
9350
|
+
* sides are loopback http. A presented URI carrying a fragment or a dot segment
|
|
9351
|
+
* matches nothing — registration already refuses both, so this is the request
|
|
9352
|
+
* side of the same two rules.
|
|
6286
9353
|
*/
|
|
6287
|
-
|
|
6288
|
-
/**
|
|
6289
|
-
* Default session TTL in seconds or duration string
|
|
6290
|
-
*
|
|
6291
|
-
* Supports:
|
|
6292
|
-
* - Number: seconds (e.g., 2592000)
|
|
6293
|
-
* - String: '30d', '12h', '45m', '3600s'
|
|
6294
|
-
*
|
|
6295
|
-
* @default 7d (7 days)
|
|
6296
|
-
*/
|
|
6297
|
-
sessionTtl?: string | number;
|
|
6298
|
-
/**
|
|
6299
|
-
* App-injected validator that runs before a new user row is created,
|
|
6300
|
-
* on every registration channel (credentials, oauth, invitation).
|
|
6301
|
-
*
|
|
6302
|
-
* Throw to reject the registration — RegistrationRejectedError (403) is
|
|
6303
|
-
* the recommended error; any HttpError subclass keeps its own status.
|
|
6304
|
-
* Runs after built-in checks (verification token, duplicate account),
|
|
6305
|
-
* so existing error precedence is unchanged. Not called for admin
|
|
6306
|
-
* seeding (initializeAuth) or when linking a social account to an
|
|
6307
|
-
* existing user.
|
|
6308
|
-
*
|
|
6309
|
-
* Runs inside the registration DB transaction on every channel — keep it
|
|
6310
|
-
* fast. A slow call (e.g. an external policy API) holds a pooled DB
|
|
6311
|
-
* connection open for its full duration on every signup.
|
|
6312
|
-
*
|
|
6313
|
-
* @example
|
|
6314
|
-
* ```typescript
|
|
6315
|
-
* configureAuth({
|
|
6316
|
-
* beforeRegister: async ({ channel, metadata }) =>
|
|
6317
|
-
* {
|
|
6318
|
-
* if (channel === 'credentials' && !isOldEnough(metadata?.birthDate))
|
|
6319
|
-
* {
|
|
6320
|
-
* throw new RegistrationRejectedError({ message: 'Age requirement not met' });
|
|
6321
|
-
* }
|
|
6322
|
-
* },
|
|
6323
|
-
* });
|
|
6324
|
-
* ```
|
|
6325
|
-
*/
|
|
6326
|
-
beforeRegister?: (context: BeforeRegisterContext) => void | Promise<void>;
|
|
6327
|
-
}
|
|
9354
|
+
declare function matchesRegisteredRedirectUri(presented: string, registered: string[]): boolean;
|
|
6328
9355
|
/**
|
|
6329
|
-
*
|
|
9356
|
+
* The host shown on the consent screen, so the person approving can see where
|
|
9357
|
+
* the code is about to be sent.
|
|
9358
|
+
*/
|
|
9359
|
+
declare function redirectHostOf(uri: string): string;
|
|
9360
|
+
|
|
9361
|
+
/**
|
|
9362
|
+
* OAuth 2.1 resource indicator (RFC 8707)
|
|
6330
9363
|
*
|
|
6331
|
-
*
|
|
9364
|
+
* The one string that says which API an access token is good against. It is
|
|
9365
|
+
* required at authorize, defaulted from the grant at token, and compared again
|
|
9366
|
+
* at verification — and the three comparisons have to agree, or a token minted
|
|
9367
|
+
* for one spelling of a URL is refused by another.
|
|
6332
9368
|
*
|
|
6333
|
-
*
|
|
6334
|
-
*
|
|
6335
|
-
*
|
|
6336
|
-
*
|
|
6337
|
-
*
|
|
6338
|
-
*
|
|
9369
|
+
* So there is one rule and it is applied at every entry point: parse and
|
|
9370
|
+
* re-serialise with `new URL(...).toString()`, store that, and compare the
|
|
9371
|
+
* normalised strings exactly. That folds away the differences a URL parser is
|
|
9372
|
+
* allowed to fold — a default port written out, a percent-encoding case, an
|
|
9373
|
+
* empty path becoming `/` — and folds away nothing else. A trailing slash is
|
|
9374
|
+
* not noise: `https://api.example.com/mcp` and `https://api.example.com/mcp/`
|
|
9375
|
+
* are different paths and stay different resources.
|
|
9376
|
+
*
|
|
9377
|
+
* A fragment is refused rather than stripped. RFC 8707 §2 forbids one, and
|
|
9378
|
+
* stripping would quietly hand back a token for a target the client did not ask
|
|
9379
|
+
* for.
|
|
6339
9380
|
*/
|
|
6340
|
-
declare function configureAuth(config: AuthConfig): void;
|
|
6341
9381
|
/**
|
|
6342
|
-
*
|
|
9382
|
+
* The canonical form of a resource indicator, or null when the value is not one.
|
|
9383
|
+
*
|
|
9384
|
+
* @param value - The `resource` parameter as it arrived
|
|
6343
9385
|
*/
|
|
6344
|
-
declare function
|
|
9386
|
+
declare function normalizeResource(value: string): string | null;
|
|
6345
9387
|
/**
|
|
6346
|
-
*
|
|
9388
|
+
* Whether a presented resource names the same target as a grant's.
|
|
6347
9389
|
*
|
|
6348
|
-
*
|
|
6349
|
-
*
|
|
6350
|
-
*
|
|
9390
|
+
* Both sides go through `normalizeResource`: the grant's value was normalised
|
|
9391
|
+
* when it was stored, and normalising it again costs one parse and removes the
|
|
9392
|
+
* chance that a row written by an older rule is compared raw.
|
|
6351
9393
|
*/
|
|
6352
|
-
declare function
|
|
9394
|
+
declare function sameResource(presented: string, granted: string): boolean;
|
|
9395
|
+
|
|
6353
9396
|
/**
|
|
6354
|
-
*
|
|
9397
|
+
* OAuth 2.1 token, code and clock helpers
|
|
6355
9398
|
*
|
|
6356
|
-
*
|
|
6357
|
-
*
|
|
6358
|
-
*
|
|
6359
|
-
*
|
|
6360
|
-
*
|
|
9399
|
+
* Every secret this server issues is generated, hashed and compared here, so
|
|
9400
|
+
* there is one answer to "how long is it, what does it start with, and how is it
|
|
9401
|
+
* checked" rather than one per call site.
|
|
9402
|
+
*
|
|
9403
|
+
* Lookup is by the hash and the hash column is unique, so the database finds at
|
|
9404
|
+
* most one row — but the equality that actually authorizes is done here, in
|
|
9405
|
+
* constant time, over the hex digests. It costs a string comparison of 64
|
|
9406
|
+
* characters and it means the decision never depends on how an index compared.
|
|
9407
|
+
*
|
|
9408
|
+
* The clock helpers are the other half. Three places express a moment and they
|
|
9409
|
+
* disagree about the unit — the row stores a timestamp, `verifyAccessToken`
|
|
9410
|
+
* answers seconds since the epoch, and a token response carries the seconds
|
|
9411
|
+
* remaining — so all three go through one function each rather than three
|
|
9412
|
+
* divisions by 1000 that can each be wrong on their own.
|
|
6361
9413
|
*/
|
|
6362
|
-
|
|
9414
|
+
/** Shape of an access token. Paired with `isAccessTokenShaped` below. */
|
|
9415
|
+
declare const OAUTH2_ACCESS_TOKEN_PREFIX = "spfn_at_";
|
|
9416
|
+
/** Shape of a refresh token. */
|
|
9417
|
+
declare const OAUTH2_REFRESH_TOKEN_PREFIX = "spfn_rt_";
|
|
9418
|
+
/** SHA-256 hex of a token or code. The only form either is ever stored in. */
|
|
9419
|
+
declare function hashOAuth2Secret(secret: string): string;
|
|
9420
|
+
/** Constant-time equality for two hex digests. */
|
|
9421
|
+
declare function sameOAuth2Hash(a: string, b: string): boolean;
|
|
9422
|
+
declare function generateAccessToken(): string;
|
|
9423
|
+
declare function generateRefreshToken(): string;
|
|
9424
|
+
/** 43 url-safe characters, which is 32 bytes of base64url with no padding. */
|
|
9425
|
+
declare function generateAuthorizationCode(): string;
|
|
9426
|
+
/**
|
|
9427
|
+
* Whether a presented bearer credential has the access-token shape.
|
|
9428
|
+
*
|
|
9429
|
+
* Shape only — the token may still be unknown, revoked or expired, which
|
|
9430
|
+
* `verifyAccessToken` decides. Callers read a raw header, so a missing or
|
|
9431
|
+
* non-string argument answers false rather than throwing, exactly as
|
|
9432
|
+
* `isOpsToken` does.
|
|
9433
|
+
*/
|
|
9434
|
+
declare function isAccessTokenShaped(bearer: string): boolean;
|
|
9435
|
+
/** The PKCE S256 transform: base64url of SHA-256 over the verifier's ASCII. */
|
|
9436
|
+
declare function pkceChallengeFor(codeVerifier: string): string;
|
|
9437
|
+
/**
|
|
9438
|
+
* Whether a presented verifier is one RFC 7636 describes.
|
|
9439
|
+
*
|
|
9440
|
+
* Conformance rather than defence — the S256 comparison is what authorizes, and
|
|
9441
|
+
* a verifier of any length still has to hash to the stored challenge. What this
|
|
9442
|
+
* catches is a client that built its verifier wrong: 22 characters of entropy
|
|
9443
|
+
* carries less than the transform promises, and an oversized one is a client
|
|
9444
|
+
* that will be refused by the next server it talks to for a reason this one
|
|
9445
|
+
* never told it.
|
|
9446
|
+
*/
|
|
9447
|
+
declare function isPkceVerifierShaped(codeVerifier: string): boolean;
|
|
9448
|
+
/**
|
|
9449
|
+
* Whether a presented challenge could have come out of the S256 transform.
|
|
9450
|
+
*
|
|
9451
|
+
* A challenge of any other shape is a client that sent something else in the
|
|
9452
|
+
* field — its verifier, most often, which is `plain` PKCE wearing the S256
|
|
9453
|
+
* label and is exactly what the method check exists to refuse.
|
|
9454
|
+
*/
|
|
9455
|
+
declare function isPkceS256ChallengeShaped(codeChallenge: string): boolean;
|
|
9456
|
+
/** A moment as seconds since the epoch — what `verifyAccessToken` answers with. */
|
|
9457
|
+
declare function toEpochSeconds(at: Date): number;
|
|
9458
|
+
/**
|
|
9459
|
+
* Whole seconds from now until a moment — what `expires_in` carries.
|
|
9460
|
+
*
|
|
9461
|
+
* Floored at zero: a token that expired while the response was being built is
|
|
9462
|
+
* answered as zero rather than as a negative number a client would read as an
|
|
9463
|
+
* enormous unsigned one.
|
|
9464
|
+
*/
|
|
9465
|
+
declare function secondsUntil(at: Date, from?: Date): number;
|
|
6363
9466
|
|
|
6364
9467
|
/**
|
|
6365
9468
|
* One-Time Token Manager
|
|
@@ -6462,6 +9565,107 @@ declare function configureDeletion(options?: Partial<AuthDeletionConfig>): void;
|
|
|
6462
9565
|
*/
|
|
6463
9566
|
declare function getDeletionConfig(): AuthDeletionConfig;
|
|
6464
9567
|
|
|
9568
|
+
/**
|
|
9569
|
+
* Device-code Login Configuration
|
|
9570
|
+
*
|
|
9571
|
+
* Singleton config for device-code login, mirroring `lib/deletion-config.ts`: a
|
|
9572
|
+
* mutable module-level value, set once during `createAuthLifecycle()` and read at
|
|
9573
|
+
* request time.
|
|
9574
|
+
*
|
|
9575
|
+
* Both knobs are answers the server hands to a client that cannot ask again — the
|
|
9576
|
+
* waiting device is told its expiry and its poll interval in the `start` response
|
|
9577
|
+
* and then just obeys them — so they have to be resolved before any request is
|
|
9578
|
+
* served, not read from an environment variable at each use.
|
|
9579
|
+
*/
|
|
9580
|
+
interface AuthDeviceAuthConfig {
|
|
9581
|
+
/** How long a device code stays usable, in milliseconds. */
|
|
9582
|
+
ttlMs: number;
|
|
9583
|
+
/** How long the waiting device should wait between polls, in milliseconds. */
|
|
9584
|
+
intervalMs: number;
|
|
9585
|
+
}
|
|
9586
|
+
/**
|
|
9587
|
+
* Ten minutes. Long enough to walk to the other device and type the code, short
|
|
9588
|
+
* enough that a code glimpsed over a shoulder is worthless soon after.
|
|
9589
|
+
*/
|
|
9590
|
+
declare const DEFAULT_DEVICE_AUTH_TTL_MS: number;
|
|
9591
|
+
/** Five seconds. The interval the server asks for; the rate limit is what enforces it. */
|
|
9592
|
+
declare const DEFAULT_DEVICE_AUTH_INTERVAL_MS: number;
|
|
9593
|
+
/**
|
|
9594
|
+
* Set the resolved device-auth config. Called synchronously from
|
|
9595
|
+
* `createAuthLifecycle()` for the same reason `configureDeletion` is: it must
|
|
9596
|
+
* take effect before any handler that reads `getDeviceAuthConfig()` can run.
|
|
9597
|
+
*
|
|
9598
|
+
* Both knobs are whole millisecond counts, and one that is not is refused here
|
|
9599
|
+
* rather than served. `intervalMs` is the reason the check exists: it leaves as
|
|
9600
|
+
* `intervalMillis` in the start and poll answers, which `DeviceAuthPollResponseSchema`
|
|
9601
|
+
* declares an integer and the mobile contract exports as one, so a fractional
|
|
9602
|
+
* value configured here would reach a generated client as a number its decoder
|
|
9603
|
+
* refuses — at the one moment the flow depends on that client asking again.
|
|
9604
|
+
*/
|
|
9605
|
+
declare function configureDeviceAuth(options?: Partial<AuthDeviceAuthConfig>): void;
|
|
9606
|
+
/**
|
|
9607
|
+
* Read the current device-auth config. Safe to call any time — defaults apply
|
|
9608
|
+
* even if `createAuthLifecycle()` was never given a `deviceAuth` block.
|
|
9609
|
+
*/
|
|
9610
|
+
declare function getDeviceAuthConfig(): AuthDeviceAuthConfig;
|
|
9611
|
+
|
|
9612
|
+
/**
|
|
9613
|
+
* @spfn/auth - Device code generation
|
|
9614
|
+
*
|
|
9615
|
+
* The two codes device-code login runs on, and the one-way function between the
|
|
9616
|
+
* device code and what is stored for it.
|
|
9617
|
+
*
|
|
9618
|
+
* They are different kinds of value and are treated differently everywhere. The
|
|
9619
|
+
* device code is a credential: the polling device holds it, nothing else proves
|
|
9620
|
+
* who that device is, so it is long, random, and stored only as a hash. The user
|
|
9621
|
+
* code is read off one screen and typed on another, so it is short and stored in
|
|
9622
|
+
* the clear — it authorizes nothing on its own, since only an already
|
|
9623
|
+
* authenticated caller can act on it.
|
|
9624
|
+
*/
|
|
9625
|
+
/**
|
|
9626
|
+
* Alphabet the user code is drawn from.
|
|
9627
|
+
*
|
|
9628
|
+
* 0/O and 1/I/L are the pairs a person mistypes when copying a code between two
|
|
9629
|
+
* screens, so neither member of either pair is in the set. 31 characters over 8
|
|
9630
|
+
* positions is ~2^39 codes, which the per-IP and per-account rate limits on
|
|
9631
|
+
* every route that accepts one make unguessable in the ten minutes a code lives.
|
|
9632
|
+
*/
|
|
9633
|
+
declare const USER_CODE_ALPHABET = "ABCDEFGHJKMNPQRSTUVWXYZ23456789";
|
|
9634
|
+
/** Characters in a user code, not counting the dash it is displayed with. */
|
|
9635
|
+
declare const USER_CODE_LENGTH = 8;
|
|
9636
|
+
/**
|
|
9637
|
+
* A fresh user code in stored form — uppercase, no dash.
|
|
9638
|
+
*
|
|
9639
|
+
* `randomInt` rather than `Math.random`: this is drawn from the same CSPRNG the
|
|
9640
|
+
* device code is, and a predictable user code lets an attacker have a victim
|
|
9641
|
+
* approve a code the attacker is polling.
|
|
9642
|
+
*/
|
|
9643
|
+
declare function generateUserCode(): string;
|
|
9644
|
+
/** The stored form as a person reads it: `XXXX-XXXX`. */
|
|
9645
|
+
declare function formatUserCode(userCode: string): string;
|
|
9646
|
+
/**
|
|
9647
|
+
* Fold a typed user code back to the stored form.
|
|
9648
|
+
*
|
|
9649
|
+
* Someone reading `WXYZ-2345` off a screen types the dash, or spaces, or lower
|
|
9650
|
+
* case. All of those name the same code, so all of them have to reach the same
|
|
9651
|
+
* lookup — the alternative is a code that is on screen and still refused.
|
|
9652
|
+
*
|
|
9653
|
+
* Only the user code is normalized. The device code is a credential and is
|
|
9654
|
+
* matched byte for byte: accepting variant spellings of a credential is how one
|
|
9655
|
+
* token becomes several strings and a one-shot record is used twice.
|
|
9656
|
+
*/
|
|
9657
|
+
declare function normalizeUserCode(input: string): string;
|
|
9658
|
+
/** A fresh device code, returned to the new device once and never stored. */
|
|
9659
|
+
declare function generateDeviceCode(): string;
|
|
9660
|
+
/**
|
|
9661
|
+
* What the row holds instead of the device code.
|
|
9662
|
+
*
|
|
9663
|
+
* Unsalted SHA-256, as with the ops-token and signup-link secrets: the input is
|
|
9664
|
+
* 256 bits of CSPRNG output, so there is no dictionary to defend against and a
|
|
9665
|
+
* per-row salt would only stop the exact-match lookup this has to support.
|
|
9666
|
+
*/
|
|
9667
|
+
declare function hashDeviceCode(deviceCode: string): string;
|
|
9668
|
+
|
|
6465
9669
|
/**
|
|
6466
9670
|
* @spfn/auth - Centralized Logger
|
|
6467
9671
|
*
|
|
@@ -6475,6 +9679,7 @@ declare const authLogger: {
|
|
|
6475
9679
|
login: _spfn_core_logger.Logger;
|
|
6476
9680
|
keyRotation: _spfn_core_logger.Logger;
|
|
6477
9681
|
oauth: _spfn_core_logger.Logger;
|
|
9682
|
+
csrf: _spfn_core_logger.Logger;
|
|
6478
9683
|
};
|
|
6479
9684
|
session: _spfn_core_logger.Logger;
|
|
6480
9685
|
service: _spfn_core_logger.Logger;
|
|
@@ -6667,6 +9872,65 @@ interface AuthLifecycleOptions extends AuthInitOptions {
|
|
|
6667
9872
|
*/
|
|
6668
9873
|
onBeforePurge?: (user: AccountDeletionPurgeUser) => Promise<void>;
|
|
6669
9874
|
};
|
|
9875
|
+
/**
|
|
9876
|
+
* Device-code login configuration
|
|
9877
|
+
*
|
|
9878
|
+
* Controls the codes handed out by `POST /_auth/device/start` and obeyed by
|
|
9879
|
+
* `POST /_auth/device/poll`. Both values are announced to the waiting device
|
|
9880
|
+
* in the start response, so they are resolved here rather than read per call,
|
|
9881
|
+
* and both must be a positive whole number of milliseconds — the interval is
|
|
9882
|
+
* published as an integer and a fraction is refused at startup rather than
|
|
9883
|
+
* handed to a client that cannot decode it.
|
|
9884
|
+
*
|
|
9885
|
+
* @example
|
|
9886
|
+
* ```typescript
|
|
9887
|
+
* createAuthLifecycle({
|
|
9888
|
+
* deviceAuth: {
|
|
9889
|
+
* ttlMs: 5 * 60 * 1000, // a code lives 5 minutes
|
|
9890
|
+
* intervalMs: 3000, // poll every 3 seconds
|
|
9891
|
+
* },
|
|
9892
|
+
* })
|
|
9893
|
+
* ```
|
|
9894
|
+
*/
|
|
9895
|
+
deviceAuth?: {
|
|
9896
|
+
/**
|
|
9897
|
+
* How long a device code stays usable, in milliseconds.
|
|
9898
|
+
* @default 600000 (10 minutes)
|
|
9899
|
+
*/
|
|
9900
|
+
ttlMs?: number;
|
|
9901
|
+
/**
|
|
9902
|
+
* Poll interval the server asks the waiting device to keep, in milliseconds.
|
|
9903
|
+
* @default 5000
|
|
9904
|
+
*/
|
|
9905
|
+
intervalMs?: number;
|
|
9906
|
+
};
|
|
9907
|
+
/**
|
|
9908
|
+
* OAuth 2.1 authorization server for MCP clients
|
|
9909
|
+
*
|
|
9910
|
+
* Opt-in, and the opt-in is this block. Without it `/_auth/oauth2/*` and
|
|
9911
|
+
* `/.well-known/oauth-authorization-server` answer 404, the boot check does
|
|
9912
|
+
* not run, and the application boots exactly as it did before this feature
|
|
9913
|
+
* existed. With it, Claude Code and Codex can register themselves, send the
|
|
9914
|
+
* account owner to a consent screen, and hold a token against `/mcp`.
|
|
9915
|
+
*
|
|
9916
|
+
* `scopes` is the one thing with no default: the names are the
|
|
9917
|
+
* application's own vocabulary, they are published in the metadata document
|
|
9918
|
+
* and shown on the consent screen, and there is nothing to derive them from.
|
|
9919
|
+
*
|
|
9920
|
+
* @example
|
|
9921
|
+
* ```typescript
|
|
9922
|
+
* createAuthLifecycle({
|
|
9923
|
+
* authorizationServer: {
|
|
9924
|
+
* scopes: {
|
|
9925
|
+
* 'mcp:read': 'Read your projects and tasks',
|
|
9926
|
+
* 'mcp:write': 'Create and edit your tasks',
|
|
9927
|
+
* },
|
|
9928
|
+
* defaultScopes: ['mcp:read'],
|
|
9929
|
+
* },
|
|
9930
|
+
* })
|
|
9931
|
+
* ```
|
|
9932
|
+
*/
|
|
9933
|
+
authorizationServer?: AuthorizationServerOptions;
|
|
6670
9934
|
}
|
|
6671
9935
|
declare function createAuthLifecycle(options?: AuthLifecycleOptions): AuthLifecycleConfig;
|
|
6672
9936
|
|
|
@@ -6698,8 +9962,9 @@ declare function createAuthLifecycle(options?: AuthLifecycleOptions): AuthLifecy
|
|
|
6698
9962
|
* statements). So the static `authJobRouter` export below always uses the default
|
|
6699
9963
|
* cron — `deletion.purgeCron` passed to `createAuthLifecycle()` cannot reach it.
|
|
6700
9964
|
* To use a custom cron, build the router yourself, after calling
|
|
6701
|
-
* `createAuthLifecycle()`, with `
|
|
6702
|
-
*
|
|
9965
|
+
* `createAuthLifecycle()`, with `createAuthJobRouter({ purgeCron })` and register
|
|
9966
|
+
* that instead of the static export. The router itself lives in `./index.ts`,
|
|
9967
|
+
* which also carries `auth.link-mail`.
|
|
6703
9968
|
*/
|
|
6704
9969
|
/**
|
|
6705
9970
|
* Build the `auth.deletion.purge` job with a given cron schedule.
|
|
@@ -6707,24 +9972,82 @@ declare function createAuthLifecycle(options?: AuthLifecycleOptions): AuthLifecy
|
|
|
6707
9972
|
* @param cronExpression - Defaults to `deletion-config`'s default (daily at 04:00).
|
|
6708
9973
|
*/
|
|
6709
9974
|
declare function createAuthDeletionPurgeJob(cronExpression?: string): _spfn_core_job.JobDef<void, void>;
|
|
9975
|
+
|
|
9976
|
+
/**
|
|
9977
|
+
* Send the mail one request deferred.
|
|
9978
|
+
*
|
|
9979
|
+
* The three kinds share a queue because they share a reason to exist: each is a
|
|
9980
|
+
* mail whose presence or absence would otherwise be visible in how long a
|
|
9981
|
+
* request took.
|
|
9982
|
+
*/
|
|
9983
|
+
declare const linkMailJob: _spfn_core_job.JobDef<{
|
|
9984
|
+
kind: "signup-link";
|
|
9985
|
+
rowId: number;
|
|
9986
|
+
} | {
|
|
9987
|
+
kind: "password-reset";
|
|
9988
|
+
rowId: number;
|
|
9989
|
+
} | {
|
|
9990
|
+
target: string;
|
|
9991
|
+
targetType: "email" | "phone";
|
|
9992
|
+
kind: "account-exists";
|
|
9993
|
+
}, void>;
|
|
9994
|
+
|
|
6710
9995
|
/**
|
|
6711
|
-
* Build
|
|
6712
|
-
*
|
|
6713
|
-
*
|
|
6714
|
-
*
|
|
9996
|
+
* Build the auth job router: the account-deletion purge sweep, the OAuth 2.1
|
|
9997
|
+
* stale-client sweep, and the link-mail sender.
|
|
9998
|
+
*
|
|
9999
|
+
* The cron is a parameter because `job(...).cron(expression)` bakes the string in
|
|
10000
|
+
* at module-import time, which always happens before `createAuthLifecycle()` runs
|
|
10001
|
+
* in the app's own module — so the static `authJobRouter` export below cannot
|
|
10002
|
+
* pick up a `deletion.purgeCron` given to the lifecycle. Build the router here,
|
|
10003
|
+
* after that call, when the schedule is not the default.
|
|
10004
|
+
*
|
|
10005
|
+
* @param options.purgeCron - Deletion purge schedule; defaults to daily at 04:00
|
|
10006
|
+
* @param options.oauth2ClientPurgeCron - Stale OAuth client sweep; defaults to daily at 05:00
|
|
6715
10007
|
*/
|
|
6716
|
-
declare function
|
|
10008
|
+
declare function createAuthJobRouter(options?: {
|
|
6717
10009
|
purgeCron?: string;
|
|
10010
|
+
oauth2ClientPurgeCron?: string;
|
|
6718
10011
|
}): _spfn_core_job.JobRouter<{
|
|
6719
10012
|
deletionPurge: _spfn_core_job.JobDef<void, void>;
|
|
10013
|
+
oauth2ClientPurge: _spfn_core_job.JobDef<void, void>;
|
|
10014
|
+
linkMail: _spfn_core_job.JobDef<{
|
|
10015
|
+
kind: "signup-link";
|
|
10016
|
+
rowId: number;
|
|
10017
|
+
} | {
|
|
10018
|
+
kind: "password-reset";
|
|
10019
|
+
rowId: number;
|
|
10020
|
+
} | {
|
|
10021
|
+
target: string;
|
|
10022
|
+
targetType: "email" | "phone";
|
|
10023
|
+
kind: "account-exists";
|
|
10024
|
+
}, void>;
|
|
6720
10025
|
}>;
|
|
6721
10026
|
/**
|
|
6722
|
-
* Default job router — the default cron (`0 4 * * *`). Register with
|
|
10027
|
+
* Default job router — the default purge cron (`0 4 * * *`). Register with
|
|
6723
10028
|
* `.jobs(authJobRouter)` in `server.config.ts`.
|
|
6724
10029
|
*/
|
|
6725
10030
|
declare const authJobRouter: _spfn_core_job.JobRouter<{
|
|
6726
10031
|
deletionPurge: _spfn_core_job.JobDef<void, void>;
|
|
10032
|
+
oauth2ClientPurge: _spfn_core_job.JobDef<void, void>;
|
|
10033
|
+
linkMail: _spfn_core_job.JobDef<{
|
|
10034
|
+
kind: "signup-link";
|
|
10035
|
+
rowId: number;
|
|
10036
|
+
} | {
|
|
10037
|
+
kind: "password-reset";
|
|
10038
|
+
rowId: number;
|
|
10039
|
+
} | {
|
|
10040
|
+
target: string;
|
|
10041
|
+
targetType: "email" | "phone";
|
|
10042
|
+
kind: "account-exists";
|
|
10043
|
+
}, void>;
|
|
6727
10044
|
}>;
|
|
10045
|
+
/**
|
|
10046
|
+
* @deprecated Renamed to `createAuthJobRouter` — the router has carried more than
|
|
10047
|
+
* the deletion purge since `auth.link-mail` joined it. Same arguments, same
|
|
10048
|
+
* result; register only one auth router either way.
|
|
10049
|
+
*/
|
|
10050
|
+
declare const createAuthDeletionJobRouter: typeof createAuthJobRouter;
|
|
6728
10051
|
|
|
6729
10052
|
/**
|
|
6730
10053
|
* @spfn/auth - Auth Events
|
|
@@ -6745,6 +10068,7 @@ declare const AuthProviderSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TL
|
|
|
6745
10068
|
* 발행 시점:
|
|
6746
10069
|
* - 이메일/전화 로그인 성공 시
|
|
6747
10070
|
* - OAuth 기존 사용자 로그인 시
|
|
10071
|
+
* - 기기 코드 승인이 소비되어 새 기기 키가 등록될 때 (provider: 'device')
|
|
6748
10072
|
*
|
|
6749
10073
|
* @example
|
|
6750
10074
|
* ```typescript
|
|
@@ -6757,7 +10081,7 @@ declare const authLoginEvent: _spfn_core_event.EventDef<{
|
|
|
6757
10081
|
email?: string | undefined;
|
|
6758
10082
|
phone?: string | undefined;
|
|
6759
10083
|
userId: string;
|
|
6760
|
-
provider: "email" | "phone" | "google" | "apple" | "github" | "kakao" | "naver" | "superself";
|
|
10084
|
+
provider: "email" | "phone" | "google" | "apple" | "github" | "kakao" | "naver" | "superself" | "device" | "passkey";
|
|
6761
10085
|
}>;
|
|
6762
10086
|
/**
|
|
6763
10087
|
* auth.register - 회원가입 성공 이벤트
|
|
@@ -6807,8 +10131,8 @@ declare const invitationCreatedEvent: _spfn_core_event.EventDef<{
|
|
|
6807
10131
|
[x: string]: unknown;
|
|
6808
10132
|
} | undefined;
|
|
6809
10133
|
email: string;
|
|
6810
|
-
roleId: number;
|
|
6811
10134
|
token: string;
|
|
10135
|
+
roleId: number;
|
|
6812
10136
|
invitedBy: string;
|
|
6813
10137
|
expiresAt: string;
|
|
6814
10138
|
invitationId: string;
|
|
@@ -6902,11 +10226,34 @@ declare const oauthUnlinkedEvent: _spfn_core_event.EventDef<{
|
|
|
6902
10226
|
provider: "email" | "phone" | "google" | "apple" | "github" | "kakao" | "naver" | "superself";
|
|
6903
10227
|
providerUserId: string;
|
|
6904
10228
|
}>;
|
|
10229
|
+
/**
|
|
10230
|
+
* auth.password.reset — an account's password was replaced through a reset link
|
|
10231
|
+
*
|
|
10232
|
+
* 발행 시점:
|
|
10233
|
+
* - completePasswordResetService()가 커밋된 직후
|
|
10234
|
+
*
|
|
10235
|
+
* Distinct from a password *change*, which is made from a session that already
|
|
10236
|
+
* proved itself. This one is made by whoever opened a link in a mailbox, so it
|
|
10237
|
+
* is the event an app hangs a "your password was reset" notice on — and the
|
|
10238
|
+
* signal to look at, if the owner says they did not ask for it.
|
|
10239
|
+
*
|
|
10240
|
+
* @example
|
|
10241
|
+
* ```typescript
|
|
10242
|
+
* authPasswordResetEvent.subscribe(async (payload) => {
|
|
10243
|
+
* await notifyOwner(payload.userId, 'Your password was reset');
|
|
10244
|
+
* });
|
|
10245
|
+
* ```
|
|
10246
|
+
*/
|
|
10247
|
+
declare const authPasswordResetEvent: _spfn_core_event.EventDef<{
|
|
10248
|
+
email: string;
|
|
10249
|
+
userId: string;
|
|
10250
|
+
}>;
|
|
6905
10251
|
/**
|
|
6906
10252
|
* Auth event payload types
|
|
6907
10253
|
*/
|
|
6908
10254
|
type AuthLoginPayload = typeof authLoginEvent._payload;
|
|
6909
10255
|
type AuthRegisterPayload = typeof authRegisterEvent._payload;
|
|
10256
|
+
type AuthPasswordResetPayload = typeof authPasswordResetEvent._payload;
|
|
6910
10257
|
type InvitationCreatedPayload = typeof invitationCreatedEvent._payload;
|
|
6911
10258
|
type InvitationAcceptedPayload = typeof invitationAcceptedEvent._payload;
|
|
6912
10259
|
type AuthDeletionRequestedPayload = typeof authDeletionRequestedEvent._payload;
|
|
@@ -6914,4 +10261,4 @@ type AuthDeletionCancelledPayload = typeof authDeletionCancelledEvent._payload;
|
|
|
6914
10261
|
type AuthDeletionCompletedPayload = typeof authDeletionCompletedEvent._payload;
|
|
6915
10262
|
type OAuthUnlinkedPayload = typeof oauthUnlinkedEvent._payload;
|
|
6916
10263
|
|
|
6917
|
-
export { type AccountDeletionPurgeUser, type AccountDeletionRequest, AccountDeletionRequestedBy, AccountDeletionRequestsRepository, type AuthConfig, AuthContext, type AuthDeletionCancelledPayload, type AuthDeletionCompletedPayload, type AuthDeletionConfig, type AuthDeletionRequestedPayload, type AuthLifecycleConfig, type AuthLifecycleOptions, type AuthLoginPayload, type AuthMetadataEntity, AuthMetadataRepository, AuthProviderSchema, type AuthRegisterPayload, type BeforeRegisterContext, COOKIE_NAMES, type CancelAccountDeletionParams, type CancelAccountDeletionResult, type CreateOAuthStateParams, DEFAULT_DELETION_ALLOW_SELF_IMMEDIATE, DEFAULT_DELETION_GRACE_PERIOD_DAYS, DEFAULT_DELETION_PURGE_CRON, DEFAULT_DELETION_PURGE_STRATEGY, DEFAULT_DELETION_SEND_NOTIFICATIONS, type DecryptedOAuthToken, EnvironmentKeyringTokenCipher, type GoogleTokenResponse, type GoogleUserInfo, type Invitation, type InvitationAcceptedPayload, type InvitationCreatedPayload, InvitationStatus, InvitationsRepository, type IssuedOpsToken, KeyAlgorithmType, KeysRepository, type NewAccountDeletionRequest, type NewAuthMetadataEntity, type NewInvitation, type NewOpsToken, type NewPermission, type NewPermissionEntity, type NewRole, type NewRoleEntity, type NewRolePermission, type NewUser, type NewUserPermission, type NewUserProfile, type NewUserPublicKey, type NewUserSocialAccount, type NewVerificationCode, OAuthProvider, type OAuthState, type OAuthTokenCipher, type OAuthTokenContext, type OAuthTokenType, type OAuthUnlinkedPayload, type OpsToken, OpsTokensRepository, type PendingDeletionInfo, type Permission, type PermissionEntity, PermissionsRepository, PurgeStrategy, type PurgeUserResult, type RegisterChannel, type RequestAccountDeletionParams, type RequestAccountDeletionResult, type Role, type RoleEntity, type RoleGuardOptions, type RolePermission, RolePermissionsRepository, RolesRepository, type SessionPayload, SocialAccountsRepository, SocialProvider, type SweepDuePurgesResult, type TokenPayload, type UpdateProfileParams, type User, type UserPermission, UserPermissionsRepository, type UserProfile, UserProfilesRepository, type UserPublicKey, type UserSocialAccount, UsersRepository, type VerificationCode, VerificationCodesRepository, VerificationPurpose, type VerifiedOpsToken, type VerifyIdTokenParams, acceptInvitation, accountDeletionRequests, accountDeletionRequestsRepository, addPermissionToRole, appleProvider, assertCanAssignRole, authDeletionCancelledEvent, authDeletionCompletedEvent, authDeletionRequestedEvent, authJobRouter, authLogger, authLoginEvent, authMetadata, authMetadataRepository, authRegisterEvent, authSchema, cancelAccountDeletionService, cancelInvitation, checkUsernameAvailableService, configureAuth, configureDeletion, configureOAuthTokenCipher, createAuthDeletionJobRouter, createAuthDeletionPurgeJob, createAuthLifecycle, createInvitation, createOAuthState, createRole, decodeToken, decryptToken, deleteInvitation, deleteRole, encryptToken, exchangeCodeForTokens, expireOldInvitations, generateOAuthNonce, generateToken, getAllRoles, getAuth, getAuthConfig, getAuthSessionService, getDeletionConfig, getDummyPasswordHash, getGoogleAuthUrl, getGoogleOAuthConfig, getGoogleUserInfo, getInvitationByToken, getInvitationWithDetails, getKeyId, getLocale, getOneTimeTokenManager, getOpsToken, getOptionalAuth, getPendingDeletionInfo, getRole, getRoleByName, getRolePermissions, getSessionTtl, getUser, getUserByEmailService, getUserByIdService, getUserByPhoneService, getUserId, getUserPermissions, getUserProfileService, getUserRole, githubProvider, googleProvider, hasAllPermissions, hasAnyPermission, hasAnyRole, hasPermission, hasRole, hashPassword, initOneTimeTokenManager, initializeAuth, invitationAcceptedEvent, invitationCreatedEvent, invitationsRepository, isEncrypted, isGoogleOAuthEnabled, issueOpsTokenService, kakaoProvider, keysRepository, listInvitations, listOpsTokensService, matchOAuthCsrfCookies, naverProvider, oauthUnlinkedEvent, oneTimeTokenAuth, opsTokenAuth, opsTokens, opsTokensRepository, parseDuration, permissions, permissionsRepository, purgeUserService, refreshAccessToken, removePermissionFromRole, requestAccountDeletionService, requireAnyPermission, requireOpsScope, requirePermissions, requireRole, resendInvitation, revokeOpsTokenService, roleGuard, rolePermissions, rolePermissionsRepository, roles, rolesRepository, runBeforeRegister, setRolePermissions, socialAccountsRepository, sweepDuePurges, updateLastLoginService, updateLocaleService, updateRole, updateUserProfileService, updateUserService, updateUsernameService, userInvitations, userPermissions, userPermissionsRepository, userProfiles, userProfilesRepository, userPublicKeys, userSocialAccounts, users, usersRepository, validateInvitation, validatePasswordStrength, verificationCodes, verificationCodesRepository, verifyClientToken, verifyIdToken, verifyKeyFingerprint, verifyOAuthState, verifyOpsTokenService, verifyPassword, verifyToken };
|
|
10264
|
+
export { AUTHORIZE_PATH, type AccountDeletionPurgeUser, type AccountDeletionRequest, AccountDeletionRequestedBy, AccountDeletionRequestsRepository, type AuthConfig, AuthContext, type AuthCsrfConfig, type AuthDeletionCancelledPayload, type AuthDeletionCompletedPayload, type AuthDeletionConfig, type AuthDeletionRequestedPayload, type AuthDeviceAuthConfig, type AuthLifecycleConfig, type AuthLifecycleOptions, type AuthLoginPayload, type AuthMetadataEntity, AuthMetadataRepository, type AuthPasswordResetPayload, AuthProviderSchema, type AuthRegisterPayload, type AuthorizationServerConfig, type AuthorizationServerOptions, type BeforeRegisterContext, COOKIE_NAMES, CSRF_HEADER, type CancelAccountDeletionParams, type CancelAccountDeletionResult, type CreateOAuthStateParams, type CsrfMode, DEFAULT_ACCESS_TOKEN_TTL_MS, DEFAULT_CODE_TTL_MS, DEFAULT_DELETION_ALLOW_SELF_IMMEDIATE, DEFAULT_DELETION_GRACE_PERIOD_DAYS, DEFAULT_DELETION_PURGE_CRON, DEFAULT_DELETION_PURGE_STRATEGY, DEFAULT_DELETION_SEND_NOTIFICATIONS, DEFAULT_DEVICE_AUTH_INTERVAL_MS, DEFAULT_DEVICE_AUTH_TTL_MS, DEFAULT_REFRESH_TOKEN_TTL_MS, DEVICE_AUTH_STATUSES, type DecryptedOAuthToken, type DeviceAuthStatus, type DeviceAuthorization, DeviceAuthorizationsRepository, type EmailNormalizationResult, EnvironmentKeyringTokenCipher, type GoogleTokenResponse, type GoogleUserInfo, type Invitation, type InvitationAcceptedPayload, type InvitationCreatedPayload, InvitationStatus, InvitationsRepository, type IssuedOpsToken, KeyAlgorithmType, KeysRepository, MAX_UNGRANTED_CLIENTS_PER_IP, type NewAccountDeletionRequest, type NewAuthMetadataEntity, type NewDeviceAuthorization, type NewInvitation, type NewOAuth2AuthorizationCode, type NewOAuth2Client, type NewOAuth2Grant, type NewOAuth2Token, type NewOpsToken, NewPasskey, type NewPasswordResetToken, type NewPermission, type NewPermissionEntity, type NewRole, type NewRoleEntity, type NewRolePermission, type NewSignupLinkToken, type NewUser, type NewUserPermission, type NewUserProfile, type NewUserPublicKey, type NewUserSocialAccount, type NewVerificationCode, type NewWebAuthnChallenge, OAUTH2_ACCESS_TOKEN_PREFIX, OAUTH2_REFRESH_TOKEN_PREFIX, OAUTH2_TOKEN_KINDS, type OAuth2AuthorizationCode, OAuth2AuthorizationCodesRepository, type OAuth2Client, OAuth2ClientsRepository, type OAuth2Grant, type OAuth2GrantWithClient, OAuth2GrantsRepository, type OAuth2RegisterRefusal, type OAuth2RegisterRequest, type OAuth2RegisterResult, type OAuth2RegisteredClient, type OAuth2Token, type OAuth2TokenKind, type OAuth2TokenRefusal, type OAuth2TokenRequest, type OAuth2TokenResponse, type OAuth2TokenResult, OAuth2TokensRepository, OAuthProvider, type OAuthState, type OAuthTokenCipher, type OAuthTokenContext, type OAuthTokenType, type OAuthUnlinkedPayload, OPS_TOKEN_PREFIX, type OpsOrUserConfig, type OpsToken, OpsTokensRepository, Passkey, type PasskeyConfig, type PasskeyUserVerification, PasskeysRepository, type PasswordResetToken, PasswordResetTokensRepository, type PendingDeletionInfo, type Permission, type PermissionEntity, PermissionsRepository, PurgeStrategy, type PurgeUserResult, type RedirectUriRefusal, type RegisterChannel, type RequestAccountDeletionParams, type RequestAccountDeletionResult, type Role, type RoleEntity, type RoleGuardOptions, type RolePermission, RolePermissionsRepository, RolesRepository, STALE_CLIENT_MAX_AGE_MS, SUPPORTED_GRANT_TYPES, SUPPORTED_RESPONSE_TYPES, type SessionPayload, type SignupLinkToken, SignupLinkTokensRepository, SocialAccountsRepository, SocialProvider, type SweepDuePurgesResult, type TokenPayload, USER_CODE_ALPHABET, USER_CODE_LENGTH, type UpdateProfileParams, type User, type UserPermission, UserPermissionsRepository, type UserProfile, UserProfilesRepository, type UserPublicKey, type UserSocialAccount, UsersRepository, type VerificationCode, VerificationCodesRepository, VerificationPurpose, type VerifiedOAuth2AccessToken, type VerifiedOpsToken, type VerifyIdTokenParams, WEBAUTHN_CHALLENGE_KINDS, type WebAuthnChallenge, type WebAuthnChallengeKind, WebAuthnChallengesRepository, acceptInvitation, accountDeletionRequests, accountDeletionRequestsRepository, addPermissionToRole, appleProvider, assertAuthorizationServerIssuer, assertCanAssignRole, assertKeyMatchesAlgorithm, assertPasskeyConfig, authDeletionCancelledEvent, authDeletionCompletedEvent, authDeletionRequestedEvent, authJobRouter, authLogger, authLoginEvent, authMetadata, authMetadataRepository, authPasswordResetEvent, authRegisterEvent, authSchema, cancelAccountDeletionService, cancelInvitation, checkUsernameAvailableService, configureAuth, configureAuthorizationServer, configureDeletion, configureDeviceAuth, configureOAuthTokenCipher, createAuthDeletionJobRouter, createAuthDeletionPurgeJob, createAuthJobRouter, createAuthLifecycle, createInvitation, createOAuthState, createRole, decodeToken, decryptToken, deleteInvitation, deleteRole, deriveCsrfToken, deviceAuthorizations, deviceAuthorizationsRepository, encryptToken, exchangeCodeForTokens, expireOldInvitations, formatUserCode, generateAccessToken, generateAuthorizationCode, generateDeviceCode, generateOAuthNonce, generateRefreshToken, generateToken, generateUserCode, getAllRoles, getAuth, getAuthConfig, getAuthSessionService, getAuthorizationServerConfig, getCsrfExemptPaths, getCsrfMode, getDeletionConfig, getDeviceAuthConfig, getDummyPasswordHash, getGoogleAuthUrl, getGoogleOAuthConfig, getGoogleUserInfo, getInvitationByToken, getInvitationWithDetails, getKeyId, getLocale, getOneTimeTokenManager, getOpsToken, getOptionalAuth, getPasskeyConfig, getPendingDeletionInfo, getRole, getRoleByName, getRolePermissions, getSessionTtl, getUser, getUserByEmailService, getUserByIdService, getUserByPhoneService, getUserId, getUserPermissions, getUserProfileService, getUserRole, githubProvider, googleProvider, hasAllPermissions, hasAnyPermission, hasAnyRole, hasPermission, hasRole, hashDeviceCode, hashOAuth2Secret, hashPassword, initOneTimeTokenManager, initializeAuth, invitationAcceptedEvent, invitationCreatedEvent, invitationsRepository, isAccessTokenShaped, isEncrypted, isGoogleOAuthEnabled, isLoopbackHostname, isOpsToken, isPkceS256ChallengeShaped, isPkceVerifierShaped, issueOpsTokenService, kakaoProvider, keysRepository, linkMailJob, listInvitations, listOpsTokensService, matchOAuthCsrfCookies, matchesCsrfToken, matchesRegisteredRedirectUri, naverProvider, normalizeEmail, normalizeOptionalEmail, normalizeResource, normalizeStoredEmails, normalizeUserCode, oauth2AuthorizationCodes, oauth2AuthorizationCodesRepository, oauth2Clients, oauth2ClientsRepository, oauth2Grants, oauth2GrantsRepository, oauth2TokenService, oauth2Tokens, oauth2TokensRepository, oauthUnlinkedEvent, oneTimeTokenAuth, opsOrUser, opsTokenAuth, opsTokens, opsTokensRepository, parseDuration, passkeysRepository, passwordResetTokens, passwordResetTokensRepository, permissions, permissionsRepository, pkceChallengeFor, purgeStaleOAuth2ClientsService, purgeUserService, redirectHostOf, refreshAccessToken, refuseRedirectUriRegistration, registerOAuth2ClientService, removePermissionFromRole, requestAccountDeletionService, requireAnyPermission, requireOpsScope, requirePermissions, requireRole, resendInvitation, resolveIssuerSource, revokeOAuth2TokenService, revokeOpsTokenService, roleGuard, rolePermissions, rolePermissionsRepository, roles, rolesRepository, runBeforeRegister, sameOAuth2Hash, sameResource, secondsUntil, setRolePermissions, signupLinkTokens, signupLinkTokensRepository, socialAccountsRepository, sweepDuePurges, toEpochSeconds, updateLastLoginService, updateLocaleService, updateRole, updateUserProfileService, updateUserService, updateUsernameService, userInvitations, userPermissions, userPermissionsRepository, userProfiles, userProfilesRepository, userPublicKeys, userSocialAccounts, users, usersRepository, validateInvitation, validatePasswordStrength, verificationCodes, verificationCodesRepository, verifyAccessToken, verifyClientToken, verifyIdToken, verifyKeyFingerprint, verifyOAuthState, verifyOpsTokenService, verifyPassword, verifyToken, webauthnChallenges, webauthnChallengesRepository };
|