@rebasepro/server 0.13.0 → 0.13.1-canary.g06dbe5b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{admin_block-CcSGdH7M.js → admin_block-H86dCPsj.js} +62 -2
- package/dist/admin_block-H86dCPsj.js.map +1 -0
- package/dist/api/ast-schema-editor.d.ts +74 -6
- package/dist/api/errors.d.ts +0 -6
- package/dist/api/openapi-generator.d.ts +14 -0
- package/dist/api/rest/api-generator.d.ts +1 -1
- package/dist/api/rest/idempotency.d.ts +62 -4
- package/dist/api/rest/query-parser.d.ts +15 -1
- package/dist/api/rest/write-validation.d.ts +52 -9
- package/dist/auth/adapter-middleware.d.ts +3 -1
- package/dist/auth/apple-oauth.d.ts +16 -11
- package/dist/auth/bitbucket-oauth.d.ts +2 -4
- package/dist/auth/builtin-auth-adapter.d.ts +2 -0
- package/dist/auth/discord-oauth.d.ts +5 -6
- package/dist/auth/facebook-oauth.d.ts +2 -4
- package/dist/auth/github-oauth.d.ts +2 -4
- package/dist/auth/gitlab-oauth.d.ts +6 -4
- package/dist/auth/google-oauth.d.ts +1 -0
- package/dist/auth/index.d.ts +8 -1
- package/dist/auth/interfaces.d.ts +68 -1
- package/dist/auth/jwt.d.ts +40 -0
- package/dist/auth/linkedin-oauth.d.ts +2 -4
- package/dist/auth/mfa-gate.d.ts +42 -0
- package/dist/auth/mfa-routes.d.ts +26 -1
- package/dist/auth/mfa.d.ts +16 -0
- package/dist/auth/microsoft-oauth.d.ts +19 -4
- package/dist/auth/oauth-code-flow.d.ts +66 -0
- package/dist/auth/oauth-signin-policy.d.ts +61 -0
- package/dist/auth/oidc-id-token.d.ts +61 -0
- package/dist/auth/rate-limiter.d.ts +37 -2
- package/dist/auth/rls-scope.d.ts +25 -0
- package/dist/auth/routes.d.ts +8 -0
- package/dist/auth/slack-oauth.d.ts +2 -4
- package/dist/auth/spotify-oauth.d.ts +2 -4
- package/dist/auth/twitter-oauth.d.ts +2 -5
- package/dist/{auth-CuC9M2x6.js → auth-DkAHqvf5.js} +1604 -391
- package/dist/auth-DkAHqvf5.js.map +1 -0
- package/dist/{backup-CVggVhR2.js → backup-DLluVyuA.js} +2 -2
- package/dist/{backup-CVggVhR2.js.map → backup-DLluVyuA.js.map} +1 -1
- package/dist/boot/boot.d.ts +19 -0
- package/dist/boot/ddl-bootstrap.d.ts +70 -0
- package/dist/{contract-routes-Dj8i5AiM.js → contract-routes-BB1U05sS.js} +3 -3
- package/dist/{contract-routes-Dj8i5AiM.js.map → contract-routes-BB1U05sS.js.map} +1 -1
- package/dist/cron/cron-scheduler.d.ts +8 -3
- package/dist/cron/define-cron.d.ts +17 -3
- package/dist/{cron-loader-B1S2MCSl.js → cron-loader-3U5aGILy.js} +2 -2
- package/dist/{cron-loader-B1S2MCSl.js.map → cron-loader-3U5aGILy.js.map} +1 -1
- package/dist/{cron-routes-CrQ0tK-_.js → cron-routes-rZgwlOz4.js} +2 -2
- package/dist/{cron-routes-CrQ0tK-_.js.map → cron-routes-rZgwlOz4.js.map} +1 -1
- package/dist/{cron-scheduler-B3RFt0HS.js → cron-scheduler-FJAaCAXm.js} +30 -5
- package/dist/cron-scheduler-FJAaCAXm.js.map +1 -0
- package/dist/{cron-store-BywZsyfZ.js → cron-store-9NmUDfzL.js} +66 -47
- package/dist/cron-store-9NmUDfzL.js.map +1 -0
- package/dist/ddl-bootstrap-BhXbTnBl.js +183 -0
- package/dist/ddl-bootstrap-BhXbTnBl.js.map +1 -0
- package/dist/email/html.d.ts +54 -0
- package/dist/email/index.d.ts +3 -0
- package/dist/email/link-base.d.ts +39 -0
- package/dist/email/smtp-email-service.d.ts +7 -1
- package/dist/email/templates.d.ts +9 -1
- package/dist/email/types.d.ts +11 -1
- package/dist/{errors-CgkCzoj7.js → errors-B1WZEdsK.js} +3 -11
- package/dist/errors-B1WZEdsK.js.map +1 -0
- package/dist/function-loader-D1SwtCa5.js +139 -0
- package/dist/function-loader-D1SwtCa5.js.map +1 -0
- package/dist/{function-routes-C0cLIy3N.js → function-routes-Btcez1T-.js} +18 -6
- package/dist/function-routes-Btcez1T-.js.map +1 -0
- package/dist/functions/define-function.d.ts +25 -6
- package/dist/functions/function-loader.d.ts +23 -0
- package/dist/functions/function-routes.d.ts +7 -1
- package/dist/functions/request-timeout.d.ts +34 -0
- package/dist/history/history-routes.d.ts +6 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.es.js +1960 -794
- package/dist/index.es.js.map +1 -1
- package/dist/init/docs.d.ts +10 -1
- package/dist/init/process-safety.d.ts +25 -0
- package/dist/init.d.ts +27 -0
- package/dist/{jwt-DD6EtpGj.js → jwt-Cuq6MwNy.js} +70 -8
- package/dist/jwt-Cuq6MwNy.js.map +1 -0
- package/dist/logger-DfvF_8r-.js +190 -0
- package/dist/logger-DfvF_8r-.js.map +1 -0
- package/dist/openapi-generator-BQxxxDpb.js +865 -0
- package/dist/openapi-generator-BQxxxDpb.js.map +1 -0
- package/dist/request-timeout-RivJsME0.js +65 -0
- package/dist/request-timeout-RivJsME0.js.map +1 -0
- package/dist/schema-editor-routes-DZFKGXgq.js +437 -0
- package/dist/schema-editor-routes-DZFKGXgq.js.map +1 -0
- package/dist/serve-spa.d.ts +5 -4
- package/dist/services/outbound-url-guard.d.ts +53 -0
- package/dist/services/routed-realtime-service.d.ts +10 -2
- package/dist/services/webhook-service.d.ts +76 -1
- package/dist/singleton.d.ts +25 -8
- package/dist/{src-Cum9kox5.js → src-By48Ffg0.js} +322 -70
- package/dist/src-By48Ffg0.js.map +1 -0
- package/dist/{src-_qQ3RNCK.js → src-Ca6NhxKs.js} +79 -2
- package/dist/src-Ca6NhxKs.js.map +1 -0
- package/dist/storage/LocalStorageController.d.ts +12 -1
- package/dist/storage/image-transform.d.ts +57 -0
- package/dist/storage/keys.d.ts +98 -0
- package/dist/storage/routes.d.ts +11 -0
- package/dist/storage/tus-handler.d.ts +7 -1
- package/dist/utils/logger.d.ts +18 -0
- package/dist/utils/logging.d.ts +0 -4
- package/dist/utils/sql.d.ts +11 -6
- package/package.json +7 -6
- package/dist/admin_block-CcSGdH7M.js.map +0 -1
- package/dist/auth-CuC9M2x6.js.map +0 -1
- package/dist/backend-CIxN4FVm.js +0 -15
- package/dist/backend-CIxN4FVm.js.map +0 -1
- package/dist/cron-scheduler-B3RFt0HS.js.map +0 -1
- package/dist/cron-store-BywZsyfZ.js.map +0 -1
- package/dist/errors-CgkCzoj7.js.map +0 -1
- package/dist/function-loader-B_1fYfUY.js +0 -86
- package/dist/function-loader-B_1fYfUY.js.map +0 -1
- package/dist/function-routes-C0cLIy3N.js.map +0 -1
- package/dist/jwt-DD6EtpGj.js.map +0 -1
- package/dist/logger-BYU66ENZ.js +0 -94
- package/dist/logger-BYU66ENZ.js.map +0 -1
- package/dist/openapi-generator-BEwyiaAr.js +0 -597
- package/dist/openapi-generator-BEwyiaAr.js.map +0 -1
- package/dist/schema-editor-routes-CbF20Mf1.js +0 -248
- package/dist/schema-editor-routes-CbF20Mf1.js.map +0 -1
- package/dist/src-Cum9kox5.js.map +0 -1
- package/dist/src-_qQ3RNCK.js.map +0 -1
|
@@ -55,7 +55,21 @@ export interface OAuthProviderProfile {
|
|
|
55
55
|
email: string;
|
|
56
56
|
displayName?: string | null;
|
|
57
57
|
photoUrl?: string | null;
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Whether the provider reported that it verified this email address.
|
|
60
|
+
*
|
|
61
|
+
* This is not a display field. It is the sole authorization input to
|
|
62
|
+
* account linking: a `true` here lets the sign-in route attach the
|
|
63
|
+
* incoming identity to a pre-existing account that shares the address.
|
|
64
|
+
* Set it `true` **only** from a verification signal actually present in
|
|
65
|
+
* the provider's response — pass that signal through
|
|
66
|
+
* `providerVerifiedEmail()` rather than writing a literal, so a provider
|
|
67
|
+
* that reports nothing comes back `false`.
|
|
68
|
+
*
|
|
69
|
+
* `false` is not a failure: the user signs in and, if they already have an
|
|
70
|
+
* account, links through `POST /auth/link/<provider>`, which proves
|
|
71
|
+
* ownership with a live session instead of an unverified address.
|
|
72
|
+
*/
|
|
59
73
|
emailVerified?: boolean;
|
|
60
74
|
}
|
|
61
75
|
/**
|
|
@@ -138,6 +152,15 @@ export interface RefreshTokenInfo {
|
|
|
138
152
|
revoked?: boolean;
|
|
139
153
|
/** When the sign-in happened; carried across rotations, unlike createdAt. */
|
|
140
154
|
sessionStartedAt?: Date;
|
|
155
|
+
/**
|
|
156
|
+
* The assurance level this session was established at. See
|
|
157
|
+
* {@link RefreshTokenSession.aal} — refresh reads it from here and mints
|
|
158
|
+
* the replacement access token at the same level.
|
|
159
|
+
*
|
|
160
|
+
* Absent on rows written before this column existed, and on repositories
|
|
161
|
+
* that do not store it; both read as `aal1`, the restrictive value.
|
|
162
|
+
*/
|
|
163
|
+
aal?: "aal1" | "aal2";
|
|
141
164
|
}
|
|
142
165
|
/**
|
|
143
166
|
* Identity of the sign-in a refresh token belongs to, threaded through
|
|
@@ -146,6 +169,16 @@ export interface RefreshTokenInfo {
|
|
|
146
169
|
export interface RefreshTokenSession {
|
|
147
170
|
id: string;
|
|
148
171
|
startedAt: Date;
|
|
172
|
+
/**
|
|
173
|
+
* How this session was established: `aal2` only where a second factor was
|
|
174
|
+
* actually presented.
|
|
175
|
+
*
|
|
176
|
+
* Persisted because refresh has nothing else to go on. Deriving it at
|
|
177
|
+
* refresh time from "does this user have a factor?" would promote every
|
|
178
|
+
* pre-existing password-only session of a user who later enrolled — the
|
|
179
|
+
* assurance level is a property of the *sign-in*, not of the account.
|
|
180
|
+
*/
|
|
181
|
+
aal?: "aal1" | "aal2";
|
|
149
182
|
}
|
|
150
183
|
/**
|
|
151
184
|
* Password reset token info
|
|
@@ -408,6 +441,12 @@ export interface MfaFactor {
|
|
|
408
441
|
verified: boolean;
|
|
409
442
|
createdAt: Date;
|
|
410
443
|
updatedAt: Date;
|
|
444
|
+
/**
|
|
445
|
+
* The highest TOTP time step ever accepted for this factor. A code whose
|
|
446
|
+
* step is at or below it has already been spent — see
|
|
447
|
+
* {@link MfaRepository.claimMfaFactorCounter}.
|
|
448
|
+
*/
|
|
449
|
+
lastUsedCounter?: number | null;
|
|
411
450
|
}
|
|
412
451
|
/**
|
|
413
452
|
* MFA challenge information
|
|
@@ -418,6 +457,8 @@ export interface MfaChallengeInfo {
|
|
|
418
457
|
createdAt: Date;
|
|
419
458
|
verifiedAt?: Date;
|
|
420
459
|
ipAddress?: string;
|
|
460
|
+
/** Failed verifications recorded against this challenge so far. */
|
|
461
|
+
attempts?: number;
|
|
421
462
|
}
|
|
422
463
|
/**
|
|
423
464
|
* Recovery code data structure
|
|
@@ -486,6 +527,32 @@ export interface MfaRepository {
|
|
|
486
527
|
* Check if a user has any verified MFA factors
|
|
487
528
|
*/
|
|
488
529
|
hasVerifiedMfaFactors(uid: string): Promise<boolean>;
|
|
530
|
+
/**
|
|
531
|
+
* Claim a TOTP time step for a factor: succeed once, then never again for
|
|
532
|
+
* that step or any earlier one.
|
|
533
|
+
*
|
|
534
|
+
* One statement, claim-and-act — `UPDATE … WHERE last_used_counter IS NULL
|
|
535
|
+
* OR last_used_counter < $counter RETURNING id`. A read-then-write would
|
|
536
|
+
* let two requests carrying the same code both pass the check before either
|
|
537
|
+
* wrote, which is precisely the replay this exists to stop.
|
|
538
|
+
*
|
|
539
|
+
* Optional so a repository written against an older release still compiles;
|
|
540
|
+
* where it is absent, an accepted code stays replayable for the rest of its
|
|
541
|
+
* window and the route says so in the log.
|
|
542
|
+
*
|
|
543
|
+
* @returns true when the step was claimed by this call, false when it had
|
|
544
|
+
* already been spent.
|
|
545
|
+
*/
|
|
546
|
+
claimMfaFactorCounter?(factorId: string, counter: number): Promise<boolean>;
|
|
547
|
+
/**
|
|
548
|
+
* Record a failed verification against a challenge and return the new
|
|
549
|
+
* total. Atomic (`UPDATE … SET attempts = attempts + 1 RETURNING attempts`)
|
|
550
|
+
* so concurrent guesses cannot share one increment.
|
|
551
|
+
*
|
|
552
|
+
* Optional; where it is absent the route falls back to the rate limiters
|
|
553
|
+
* alone.
|
|
554
|
+
*/
|
|
555
|
+
recordMfaChallengeAttempt?(challengeId: string): Promise<number>;
|
|
489
556
|
}
|
|
490
557
|
/**
|
|
491
558
|
* Combined auth repository interface for convenience
|
package/dist/auth/jwt.d.ts
CHANGED
|
@@ -30,6 +30,18 @@ export interface AccessTokenPayload {
|
|
|
30
30
|
* Validates the secret strength to prevent deployment with default/weak secrets.
|
|
31
31
|
*/
|
|
32
32
|
export declare function configureJwt(config: JwtConfig): void;
|
|
33
|
+
/**
|
|
34
|
+
* Has this server been given a JWT secret?
|
|
35
|
+
*
|
|
36
|
+
* False on every backend that authenticates through an adapter — Firebase,
|
|
37
|
+
* Clerk, anything with its own tokens — because nothing calls
|
|
38
|
+
* {@link configureJwt} there. Signing paths still throw when it is false, since
|
|
39
|
+
* asking for a token from a server that cannot mint one is a mistake worth
|
|
40
|
+
* hearing about. Paths whose contract is *"tolerate the absence of auth"* must
|
|
41
|
+
* ask first: `optionalAuth` crashing a request because this backend does not do
|
|
42
|
+
* JWT is a 500 on a route that had already decided anonymous was acceptable.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isJwtConfigured(): boolean;
|
|
33
45
|
/**
|
|
34
46
|
* Generate an access token (short-lived, 1 hour by default)
|
|
35
47
|
*/
|
|
@@ -86,6 +98,34 @@ export declare function getRefreshTokenTtlMs(): number;
|
|
|
86
98
|
* Calculate refresh token expiration date
|
|
87
99
|
*/
|
|
88
100
|
export declare function getRefreshTokenExpiry(): Date;
|
|
101
|
+
/**
|
|
102
|
+
* The `purpose` claim carried by a credential that stands between "first factor
|
|
103
|
+
* accepted" and "session issued".
|
|
104
|
+
*
|
|
105
|
+
* A pre-auth token is NOT a session and must never be usable as one:
|
|
106
|
+
* {@link verifyAccessToken} refuses any token carrying a `purpose`, so this
|
|
107
|
+
* value cannot authenticate a request no matter which route it is presented to.
|
|
108
|
+
* The only thing that reads it is the MFA challenge pair, which exchanges it —
|
|
109
|
+
* plus a second factor — for a real session.
|
|
110
|
+
*/
|
|
111
|
+
export declare const MFA_PENDING_PURPOSE = "mfa-pending";
|
|
112
|
+
/**
|
|
113
|
+
* Mint the short-lived credential handed back with an `MFA_REQUIRED` response.
|
|
114
|
+
*
|
|
115
|
+
* Short-lived on purpose: it is the window in which a caller who has proven the
|
|
116
|
+
* first factor may present the second, not a session to be carried around. Five
|
|
117
|
+
* minutes matches the challenge TTL.
|
|
118
|
+
*/
|
|
119
|
+
export declare function generateMfaPendingToken(uid: string, expiresInSeconds?: number): string;
|
|
120
|
+
/**
|
|
121
|
+
* Verify a pre-auth token and return the user it was minted for.
|
|
122
|
+
*
|
|
123
|
+
* Returns `null` for anything else — including a perfectly valid *access*
|
|
124
|
+
* token, which must not be interchangeable with this one in either direction.
|
|
125
|
+
*/
|
|
126
|
+
export declare function verifyMfaPendingToken(token: string): {
|
|
127
|
+
uid: string;
|
|
128
|
+
} | null;
|
|
89
129
|
export interface DownloadTokenPayload {
|
|
90
130
|
purpose: "file-read";
|
|
91
131
|
path: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OAuthProvider } from "./interfaces";
|
|
2
|
+
import { type OAuthCodeFlowPayload } from "./oauth-code-flow";
|
|
2
3
|
export interface LinkedinUserInfo {
|
|
3
4
|
linkedinId: string;
|
|
4
5
|
email: string;
|
|
@@ -12,7 +13,4 @@ export interface LinkedinUserInfo {
|
|
|
12
13
|
export declare function createLinkedinProvider(config: {
|
|
13
14
|
clientId: string;
|
|
14
15
|
clientSecret: string;
|
|
15
|
-
}): OAuthProvider<
|
|
16
|
-
code: string;
|
|
17
|
-
redirectUri: string;
|
|
18
|
-
}>;
|
|
16
|
+
}): OAuthProvider<OAuthCodeFlowPayload>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gate between "first factor accepted" and "session issued".
|
|
3
|
+
*
|
|
4
|
+
* MFA used to be modelled as an optional step-up that no resource required: a
|
|
5
|
+
* user could enrol TOTP, print recovery codes, and change nothing about what a
|
|
6
|
+
* stolen password bought, because every session-minting route issued a full
|
|
7
|
+
* `aal1` token without ever asking whether the account had a second factor.
|
|
8
|
+
* This module is the ask. It lives apart from the routes so that there is one
|
|
9
|
+
* decision, made once, that `createSessionAndTokens` cannot be extended past —
|
|
10
|
+
* a new sign-in route inherits the gate by construction rather than by the
|
|
11
|
+
* author remembering it.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import type { AuthRepository } from "./interfaces";
|
|
16
|
+
/** Error code a client watches for to switch a sign-in into its second step. */
|
|
17
|
+
export declare const MFA_REQUIRED_CODE = "MFA_REQUIRED";
|
|
18
|
+
/** Shape carried in `error.details` of an {@link MFA_REQUIRED_CODE} response. */
|
|
19
|
+
export interface MfaRequiredDetails {
|
|
20
|
+
/**
|
|
21
|
+
* Short-lived, purpose-scoped credential proving the first factor was
|
|
22
|
+
* accepted. It is refused as a session everywhere (`verifyAccessToken`
|
|
23
|
+
* rejects purpose-scoped tokens); `POST /auth/mfa/challenge` and
|
|
24
|
+
* `POST /auth/mfa/challenge/verify` are the only routes that accept it.
|
|
25
|
+
*/
|
|
26
|
+
mfaToken: string;
|
|
27
|
+
/** The verified factors the caller may answer the challenge with. */
|
|
28
|
+
factors: Array<{
|
|
29
|
+
id: string;
|
|
30
|
+
factorType: string;
|
|
31
|
+
friendlyName?: string;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Refuse to mint a session for an account whose second factor has not been
|
|
36
|
+
* presented, handing back what the client needs to present it.
|
|
37
|
+
*
|
|
38
|
+
* A 401 rather than a 200 with a different body: a client that has not been
|
|
39
|
+
* taught about MFA must fail, not proceed. The credential in `details` is
|
|
40
|
+
* useless for anything except the challenge routes.
|
|
41
|
+
*/
|
|
42
|
+
export declare function assertMfaSatisfied(authRepo: AuthRepository, uid: string): Promise<void>;
|
|
@@ -4,4 +4,29 @@ import { HonoEnv } from "../api/types";
|
|
|
4
4
|
import type { AuthModuleConfig } from "./routes";
|
|
5
5
|
import { resolveAuthHooks } from "./auth-hooks";
|
|
6
6
|
import type { AuthResponsePayload, TransformAuthResponseContext } from "@rebasepro/types";
|
|
7
|
-
|
|
7
|
+
interface MfaRoutesConfig {
|
|
8
|
+
router: Hono<HonoEnv>;
|
|
9
|
+
config: AuthModuleConfig;
|
|
10
|
+
ops: ReturnType<typeof resolveAuthHooks>;
|
|
11
|
+
parseBody: <T>(schema: z.ZodSchema<T>, body: unknown) => T;
|
|
12
|
+
buildAuthResponse: (user: {
|
|
13
|
+
id: string;
|
|
14
|
+
email: string;
|
|
15
|
+
displayName?: string | null;
|
|
16
|
+
photoUrl?: string | null;
|
|
17
|
+
emailVerified?: boolean;
|
|
18
|
+
isAnonymous?: boolean;
|
|
19
|
+
metadata?: Record<string, unknown> | null;
|
|
20
|
+
}, roleIds: string[], accessToken: string, refreshToken: string, providerId: string) => unknown;
|
|
21
|
+
createSessionAndTokens: (uid: string, userAgent: string, ipAddress: string, options?: {
|
|
22
|
+
skipMfaGate?: boolean;
|
|
23
|
+
aal?: "aal1" | "aal2";
|
|
24
|
+
}) => Promise<{
|
|
25
|
+
roleIds: string[];
|
|
26
|
+
accessToken: string;
|
|
27
|
+
refreshToken: string;
|
|
28
|
+
}>;
|
|
29
|
+
applyTransformHook?: (response: AuthResponsePayload, method: TransformAuthResponseContext["method"], request: Request, uid: string) => Promise<AuthResponsePayload>;
|
|
30
|
+
}
|
|
31
|
+
export declare function mountMfaRoutes(opts: MfaRoutesConfig): void;
|
|
32
|
+
export {};
|
package/dist/auth/mfa.d.ts
CHANGED
|
@@ -25,6 +25,22 @@ export declare function generateTotp(secret: Buffer, timeStep?: number): string;
|
|
|
25
25
|
* @returns true if the token is valid within the window
|
|
26
26
|
*/
|
|
27
27
|
export declare function verifyTotp(secret: Buffer, token: string, window?: number): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Verify a TOTP token and report *which* time step matched.
|
|
30
|
+
*
|
|
31
|
+
* RFC 6238 §5.2 requires that an accepted OTP not be accepted a second time,
|
|
32
|
+
* and a boolean cannot express what was accepted: with `window = 1` the same
|
|
33
|
+
* six digits stay valid for up to 90 seconds, so a code observed once — by a
|
|
34
|
+
* phishing relay, over a shoulder, in a pasted support message — can be
|
|
35
|
+
* presented again for the rest of that window. The caller records the returned
|
|
36
|
+
* counter against the factor and refuses anything at or below it.
|
|
37
|
+
*
|
|
38
|
+
* @param secret - The shared secret as a Buffer
|
|
39
|
+
* @param token - The 6-digit TOTP code to verify
|
|
40
|
+
* @param window - Number of time steps to check on each side (default: 1)
|
|
41
|
+
* @returns The matched time step, or `null` when no step in the window matches
|
|
42
|
+
*/
|
|
43
|
+
export declare function verifyTotpCounter(secret: Buffer, token: string, window?: number): number | null;
|
|
28
44
|
/**
|
|
29
45
|
* Generate a new TOTP secret and return the setup information
|
|
30
46
|
*
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import type { OAuthProvider } from "./interfaces";
|
|
2
|
+
import { type OAuthCodeFlowPayload } from "./oauth-code-flow";
|
|
2
3
|
/**
|
|
3
4
|
* Creates a Microsoft / Entra ID (Azure AD) OAuth Provider integration.
|
|
4
5
|
*
|
|
5
6
|
* Supports both personal Microsoft accounts and work/school (Azure AD) accounts
|
|
6
7
|
* via the "common" tenant endpoint. Uses the authorization code flow.
|
|
8
|
+
*
|
|
9
|
+
* ## On `emailVerified`
|
|
10
|
+
*
|
|
11
|
+
* Microsoft Graph exposes no email-verification field, and `mail` is *not* a
|
|
12
|
+
* substitute: it is a directory attribute a tenant administrator sets to any
|
|
13
|
+
* string they like, through Graph or AAD Connect sync. With the default
|
|
14
|
+
* `tenantId: "common"` every Entra tenant in the world is an accepted issuer,
|
|
15
|
+
* so "there is a `mail` value" would mean "anybody who can create a free
|
|
16
|
+
* tenant can nominate any address" — including one that already has an account
|
|
17
|
+
* here.
|
|
18
|
+
*
|
|
19
|
+
* The only signal Microsoft offers is the `xms_edov` ("email domain owner
|
|
20
|
+
* verified") optional claim on the id_token, so that is what this provider
|
|
21
|
+
* reads, off a signature-verified token. If the app registration does not emit
|
|
22
|
+
* `xms_edov`, sign-in still works and the address comes back unverified —
|
|
23
|
+
* which routes the user to `POST /auth/link/microsoft` rather than silently
|
|
24
|
+
* handing them somebody else's account.
|
|
7
25
|
*/
|
|
8
26
|
export declare function createMicrosoftProvider(config: {
|
|
9
27
|
clientId: string;
|
|
10
28
|
clientSecret: string;
|
|
11
29
|
/** Tenant ID. Defaults to "common" which allows both personal and organizational accounts. */
|
|
12
30
|
tenantId?: string;
|
|
13
|
-
}): OAuthProvider<
|
|
14
|
-
code: string;
|
|
15
|
-
redirectUri: string;
|
|
16
|
-
}>;
|
|
31
|
+
}): OAuthProvider<OAuthCodeFlowPayload>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The authorization-code request shape every OAuth provider accepts, and the
|
|
4
|
+
* two derivations of it that were previously copy-pasted twelve times.
|
|
5
|
+
*
|
|
6
|
+
* Twelve providers declared their own `z.object({ code, redirectUri })` and
|
|
7
|
+
* each decided independently whether to accept a PKCE verifier (one did) and
|
|
8
|
+
* how to read a verification signal off the provider's profile response (five
|
|
9
|
+
* skipped the question and hardcoded `true`). One predicate, twelve
|
|
10
|
+
* implementations — so the controls below live here and every provider calls
|
|
11
|
+
* them, which is what makes it impossible for a thirteenth provider to ship
|
|
12
|
+
* without them.
|
|
13
|
+
*/
|
|
14
|
+
/** The request body every authorization-code provider consumes. */
|
|
15
|
+
export interface OAuthCodeFlowPayload {
|
|
16
|
+
/** Authorization code returned to the client by the provider. */
|
|
17
|
+
code: string;
|
|
18
|
+
/**
|
|
19
|
+
* Redirect URI the code was issued against. Echoed to the token endpoint,
|
|
20
|
+
* which is what binds the code to the client that started the flow — and
|
|
21
|
+
* why the route allowlists it before `verify` ever runs
|
|
22
|
+
* (see `isRedirectUriAllowed`).
|
|
23
|
+
*/
|
|
24
|
+
redirectUri: string;
|
|
25
|
+
/**
|
|
26
|
+
* PKCE verifier, when the client started the flow with a
|
|
27
|
+
* `code_challenge`. Forwarded to the token endpoint verbatim.
|
|
28
|
+
*/
|
|
29
|
+
codeVerifier?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build the request schema for an authorization-code provider.
|
|
33
|
+
*
|
|
34
|
+
* `pkce` defaults to `"optional"`: the client decides whether it sent a
|
|
35
|
+
* `code_challenge`, and providers that do not implement PKCE simply never see
|
|
36
|
+
* a verifier because their clients never generate one. `"required"` is for
|
|
37
|
+
* providers that mandate PKCE (Twitter/X).
|
|
38
|
+
*/
|
|
39
|
+
export declare function oauthCodeFlowSchema(opts?: {
|
|
40
|
+
pkce?: "required" | "optional";
|
|
41
|
+
}): z.ZodObject<{
|
|
42
|
+
code: z.ZodString;
|
|
43
|
+
redirectUri: z.ZodString;
|
|
44
|
+
codeVerifier: z.ZodString | z.ZodOptional<z.ZodString>;
|
|
45
|
+
}, z.core.$strip>;
|
|
46
|
+
/**
|
|
47
|
+
* The `code_verifier` token-endpoint parameter, or nothing.
|
|
48
|
+
*
|
|
49
|
+
* Returned as a spreadable object so a provider adds PKCE with one `...` in
|
|
50
|
+
* its existing body literal, rather than branching.
|
|
51
|
+
*/
|
|
52
|
+
export declare function pkceTokenParams(codeVerifier?: string): Record<string, string>;
|
|
53
|
+
/**
|
|
54
|
+
* Normalise a provider's email-verification signal into the boolean that
|
|
55
|
+
* `OAuthProviderProfile.emailVerified` promises.
|
|
56
|
+
*
|
|
57
|
+
* The whole point is the default: anything that is not an affirmative
|
|
58
|
+
* verification signal — `undefined`, `null`, a missing field, an empty string,
|
|
59
|
+
* `false` — becomes `false`. A provider that reports nothing therefore cannot
|
|
60
|
+
* accidentally assert that it verified the address, which is exactly the
|
|
61
|
+
* mistake five providers had made.
|
|
62
|
+
*
|
|
63
|
+
* Accepts the string forms because OIDC issuers are inconsistent about whether
|
|
64
|
+
* `email_verified` is a JSON boolean or a string.
|
|
65
|
+
*/
|
|
66
|
+
export declare function providerVerifiedEmail(signal: unknown): boolean;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two decisions the OAuth sign-in route makes before it will hand a caller
|
|
3
|
+
* an existing account, extracted so they are stated once for all twelve
|
|
4
|
+
* providers and can be tested without a provider, a network or a database.
|
|
5
|
+
*/
|
|
6
|
+
/** Why an incoming OAuth identity may not be auto-attached to an existing account. */
|
|
7
|
+
export type AutoLinkRefusal =
|
|
8
|
+
/** The provider did not report that it verified the address. */
|
|
9
|
+
"provider-email-unverified"
|
|
10
|
+
/**
|
|
11
|
+
* The local account holds a password nobody ever proved they own: it was
|
|
12
|
+
* created through `POST /auth/register`, which does not verify the
|
|
13
|
+
* address. Attaching a provider identity to it would hand the session to
|
|
14
|
+
* whoever registered the address first — the classic pre-hijack.
|
|
15
|
+
*/
|
|
16
|
+
| "local-account-unverified";
|
|
17
|
+
export type AutoLinkDecision = {
|
|
18
|
+
allowed: true;
|
|
19
|
+
} | {
|
|
20
|
+
allowed: false;
|
|
21
|
+
reason: AutoLinkRefusal;
|
|
22
|
+
};
|
|
23
|
+
/** The part of an existing user row the decision depends on. */
|
|
24
|
+
export interface AutoLinkExistingUser {
|
|
25
|
+
emailVerified: boolean;
|
|
26
|
+
passwordHash?: string | null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* May an OAuth identity be attached to a pre-existing account found *by email*?
|
|
30
|
+
*
|
|
31
|
+
* Both sides have to be trustworthy, and only one of them used to be checked:
|
|
32
|
+
*
|
|
33
|
+
* - the **provider** must have verified the address, or the caller has not
|
|
34
|
+
* shown they control it;
|
|
35
|
+
* - the **local account** must itself be trustworthy — either its address was
|
|
36
|
+
* verified, or it has no password at all (it was created by an OAuth
|
|
37
|
+
* sign-in or an invitation, so there is no credential an attacker could
|
|
38
|
+
* have planted in advance).
|
|
39
|
+
*
|
|
40
|
+
* A refusal is not a dead end: `POST /auth/link/<provider>` attaches the
|
|
41
|
+
* identity once the caller proves ownership by holding a session.
|
|
42
|
+
*/
|
|
43
|
+
export declare function decideOAuthAutoLink(args: {
|
|
44
|
+
providerEmailVerified: boolean | undefined;
|
|
45
|
+
existingUser: AutoLinkExistingUser;
|
|
46
|
+
}): AutoLinkDecision;
|
|
47
|
+
/**
|
|
48
|
+
* Is `redirectUri` one the operator authorised?
|
|
49
|
+
*
|
|
50
|
+
* The provider's own registered-URI match is a real control but a coarse one:
|
|
51
|
+
* it authorises *every* URI registered on that OAuth client, so a `localhost`
|
|
52
|
+
* entry kept for development, or a second product sharing the client id, can
|
|
53
|
+
* mint codes this backend accepts. An empty or absent allowlist keeps the old
|
|
54
|
+
* behaviour (the provider's check is the only one) so existing deployments are
|
|
55
|
+
* unaffected; setting one narrows it to the origins this backend serves.
|
|
56
|
+
*
|
|
57
|
+
* Comparison is on origin plus path, with the origin lowercased and a trailing
|
|
58
|
+
* slash ignored — query strings and fragments are not part of the identity of a
|
|
59
|
+
* redirect URI, and neither is the case of the host.
|
|
60
|
+
*/
|
|
61
|
+
export declare function isRedirectUriAllowed(redirectUri: string, allowlist?: string[]): boolean;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal JWKS-backed id_token verification, shared by the OIDC providers.
|
|
4
|
+
*
|
|
5
|
+
* Apple's id_token used to be `split(".")` + `JSON.parse`, justified by the
|
|
6
|
+
* comment "we only need the payload". That skips `aud` — nothing confirmed the
|
|
7
|
+
* token was minted for *this* Services ID rather than another one under the
|
|
8
|
+
* same Apple team — along with `iss`, `exp` and the signature. Microsoft did
|
|
9
|
+
* not request an id_token at all and inferred verification from a directory
|
|
10
|
+
* attribute instead.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately hand-rolled rather than pulling in a JWKS client: the only
|
|
13
|
+
* cryptography here is `jsonwebtoken`'s (already a dependency) and Node's own
|
|
14
|
+
* JWK→KeyObject import. What this file adds is key discovery and a cache.
|
|
15
|
+
*/
|
|
16
|
+
export interface OidcIdTokenClaims {
|
|
17
|
+
sub: string;
|
|
18
|
+
iss: string;
|
|
19
|
+
aud: string | string[];
|
|
20
|
+
exp: number;
|
|
21
|
+
email?: string;
|
|
22
|
+
email_verified?: boolean | string;
|
|
23
|
+
nonce?: string;
|
|
24
|
+
[claim: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
export interface VerifyOidcIdTokenOptions {
|
|
27
|
+
/** The compact JWS to verify. */
|
|
28
|
+
idToken: string;
|
|
29
|
+
/** JWKS endpoint of the issuer. */
|
|
30
|
+
jwksUri: string;
|
|
31
|
+
/**
|
|
32
|
+
* Expected `iss`. A `RegExp` is for multi-tenant issuers whose tenant id is
|
|
33
|
+
* part of the issuer URL (Entra ID with `tenantId: "common"`); everything
|
|
34
|
+
* else passes the exact string.
|
|
35
|
+
*/
|
|
36
|
+
issuer: string | RegExp;
|
|
37
|
+
/** Expected `aud` — the client/services id this backend is configured with. */
|
|
38
|
+
audience: string;
|
|
39
|
+
/** Permitted signing algorithms. Defaults to RS256, which is what Apple and Entra use. */
|
|
40
|
+
algorithms?: jwt.Algorithm[];
|
|
41
|
+
/** Seconds of clock skew tolerated on `exp`/`iat`. Defaults to 60. */
|
|
42
|
+
clockToleranceSec?: number;
|
|
43
|
+
/** Expected `nonce`, when the client bound one to the authorization request. */
|
|
44
|
+
nonce?: string;
|
|
45
|
+
/** Injection point for tests; defaults to global `fetch`. */
|
|
46
|
+
fetchImpl?: typeof fetch;
|
|
47
|
+
}
|
|
48
|
+
/** Drops the JWKS cache. Exported for tests; nothing in the runtime calls it. */
|
|
49
|
+
export declare function resetJwksCache(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Verify an id_token's signature, `aud`, `iss` and `exp`, and return its claims.
|
|
52
|
+
*
|
|
53
|
+
* Throws on any failure — callers treat a throw as "reject this sign-in",
|
|
54
|
+
* never as "continue without the claims".
|
|
55
|
+
*/
|
|
56
|
+
export declare function verifyOidcIdToken(options: VerifyOidcIdTokenOptions): Promise<OidcIdTokenClaims>;
|
|
57
|
+
/**
|
|
58
|
+
* `verifyOidcIdToken` that reports failure as `null` and logs it, for the one
|
|
59
|
+
* call site that has to decide between "reject" and "degrade".
|
|
60
|
+
*/
|
|
61
|
+
export declare function tryVerifyOidcIdToken(providerId: string, options: VerifyOidcIdTokenOptions): Promise<OidcIdTokenClaims | null>;
|
|
@@ -54,6 +54,26 @@ export declare const defaultAuthLimiter: MiddlewareHandler<HonoEnv>;
|
|
|
54
54
|
* 50 requests per 15 minutes per IP.
|
|
55
55
|
*/
|
|
56
56
|
export declare const strictAuthLimiter: MiddlewareHandler<HonoEnv>;
|
|
57
|
+
/**
|
|
58
|
+
* Limiter for `POST /auth/send-verification`, keyed by the authenticated user.
|
|
59
|
+
*
|
|
60
|
+
* That route had no limiter at all while every one of its email-sending
|
|
61
|
+
* siblings had one, and being authenticated is not the protection it looks
|
|
62
|
+
* like: registration does not verify the address it is given, so an attacker
|
|
63
|
+
* registers a victim's address, signs in to the account they just made, and
|
|
64
|
+
* loops the route. Each call mints a token and mails the victim.
|
|
65
|
+
*
|
|
66
|
+
* An IP limiter cannot express what is wanted here — the recipient is the
|
|
67
|
+
* quantity being protected, not the caller — so this one keys on the uid, which
|
|
68
|
+
* on this route is one-to-one with the recipient address (emails are unique per
|
|
69
|
+
* account). `strictAuthLimiter` still runs in front of it to bound the caller by
|
|
70
|
+
* IP; this bounds what any single address can be sent.
|
|
71
|
+
*
|
|
72
|
+
* Five per 15 minutes is generous for "I didn't get the email, resend it" and
|
|
73
|
+
* useless as a mail bomb. Unauthenticated requests fall back to the IP bucket so
|
|
74
|
+
* the limiter is never a no-op if it is ever mounted before the auth middleware.
|
|
75
|
+
*/
|
|
76
|
+
export declare const verificationEmailLimiter: MiddlewareHandler<HonoEnv>;
|
|
57
77
|
/**
|
|
58
78
|
* Key generator for API-key-based rate limiting.
|
|
59
79
|
*
|
|
@@ -73,13 +93,28 @@ export interface DataRateLimitConfig {
|
|
|
73
93
|
user?: number;
|
|
74
94
|
/**
|
|
75
95
|
* Per IP, for requests with no principal at all. Default 300.
|
|
76
|
-
* `null` disables the anonymous bucket entirely
|
|
77
|
-
* anonymous traffic must not be throttled (public webhook functions).
|
|
96
|
+
* `null` disables the anonymous bucket entirely.
|
|
78
97
|
*/
|
|
79
98
|
anonymous?: number | null;
|
|
99
|
+
/**
|
|
100
|
+
* Per IP, for anonymous requests to `/api/functions/*`. Default 3000 —
|
|
101
|
+
* deliberately far looser than {@link anonymous}, because the functions
|
|
102
|
+
* router is public by default for webhook receivers (Stripe, GitHub) whose
|
|
103
|
+
* bursts arrive from a handful of provider IPs and would trip the data
|
|
104
|
+
* API's ceiling.
|
|
105
|
+
*
|
|
106
|
+
* The looseness bounds the *value*, not the existence of the limit: this
|
|
107
|
+
* router is the one that invites anonymous callers, so it is the last one
|
|
108
|
+
* that should have no ceiling at all. `null` disables it — an explicit
|
|
109
|
+
* choice, which is what the hardcoded `anonymous: null` here used to be
|
|
110
|
+
* without any way to say otherwise.
|
|
111
|
+
*/
|
|
112
|
+
anonymousFunctions?: number | null;
|
|
80
113
|
/** Share counts across replicas. Defaults to this process's memory. */
|
|
81
114
|
store?: RateLimitStore;
|
|
82
115
|
}
|
|
116
|
+
/** @see DataRateLimitConfig.anonymousFunctions */
|
|
117
|
+
export declare const DEFAULT_FUNCTIONS_ANONYMOUS_LIMIT = 3000;
|
|
83
118
|
/**
|
|
84
119
|
* Rate limiting for the data API.
|
|
85
120
|
*
|
package/dist/auth/rls-scope.d.ts
CHANGED
|
@@ -12,6 +12,31 @@
|
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
14
|
import type { DataDriver } from "@rebasepro/types";
|
|
15
|
+
/**
|
|
16
|
+
* The identity the trusted server plane runs as: `rebase.dataAsAdmin`, and any
|
|
17
|
+
* caller presenting the service key.
|
|
18
|
+
*
|
|
19
|
+
* Naming it makes the privilege legible, because it is narrower than it reads.
|
|
20
|
+
* This is **not** an RLS bypass — a driver scoped with it is an
|
|
21
|
+
* `AuthenticatedPostgresBackendDriver`, so every statement runs in a
|
|
22
|
+
* transaction that has done `SET LOCAL ROLE rebase_user` with
|
|
23
|
+
* `app.uid = 'service'`, and policies are evaluated. Two consequences worth
|
|
24
|
+
* knowing before you write a policy:
|
|
25
|
+
*
|
|
26
|
+
* - It passes the default policies through their `rolesOverlap(['admin'])`
|
|
27
|
+
* arm — the same arm an application user holding the `admin` role passes.
|
|
28
|
+
* - `policy.serverContext()` compiles to `auth.uid() IS NULL` and is therefore
|
|
29
|
+
* **false** for it. A collection with `disableDefaultPolicies: true` whose
|
|
30
|
+
* only rule is `serverContext()` denies these writes (42501) and returns
|
|
31
|
+
* zero rows for these reads.
|
|
32
|
+
*
|
|
33
|
+
* The true bypass is `rebase.sql()`, which runs on the owner connection and
|
|
34
|
+
* never goes near `withAuth`.
|
|
35
|
+
*/
|
|
36
|
+
export declare const SERVICE_IDENTITY: {
|
|
37
|
+
uid: string;
|
|
38
|
+
roles: string[];
|
|
39
|
+
};
|
|
15
40
|
/**
|
|
16
41
|
* Scope a DataDriver via `withAuth()` for RLS.
|
|
17
42
|
*
|
package/dist/auth/routes.d.ts
CHANGED
|
@@ -18,6 +18,14 @@ export interface AuthModuleConfig {
|
|
|
18
18
|
defaultRole?: string;
|
|
19
19
|
/** Optional array of OAuth providers */
|
|
20
20
|
oauthProviders?: OAuthProvider<unknown>[];
|
|
21
|
+
/**
|
|
22
|
+
* Redirect URIs the OAuth routes will accept, for every provider.
|
|
23
|
+
*
|
|
24
|
+
* Left unset, the only check is the provider's own registered-URI match,
|
|
25
|
+
* which authorises every URI registered on that OAuth client. Compared on
|
|
26
|
+
* origin plus path; query and fragment are ignored, as is a trailing slash.
|
|
27
|
+
*/
|
|
28
|
+
allowedRedirectUris?: string[];
|
|
21
29
|
/** When true, blocks all self-registration regardless of `allowRegistration`. */
|
|
22
30
|
disableSelfRegistration?: boolean;
|
|
23
31
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OAuthProvider } from "./interfaces";
|
|
2
|
+
import { type OAuthCodeFlowPayload } from "./oauth-code-flow";
|
|
2
3
|
/**
|
|
3
4
|
* Creates a Slack OAuth Provider integration (OAuth 2.0 / "Sign in with Slack").
|
|
4
5
|
* Uses the OpenID Connect flow with the "openid,email,profile" scopes.
|
|
@@ -6,7 +7,4 @@ import type { OAuthProvider } from "./interfaces";
|
|
|
6
7
|
export declare function createSlackProvider(config: {
|
|
7
8
|
clientId: string;
|
|
8
9
|
clientSecret: string;
|
|
9
|
-
}): OAuthProvider<
|
|
10
|
-
code: string;
|
|
11
|
-
redirectUri: string;
|
|
12
|
-
}>;
|
|
10
|
+
}): OAuthProvider<OAuthCodeFlowPayload>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OAuthProvider } from "./interfaces";
|
|
2
|
+
import { type OAuthCodeFlowPayload } from "./oauth-code-flow";
|
|
2
3
|
/**
|
|
3
4
|
* Creates a Spotify OAuth Provider integration.
|
|
4
5
|
* Uses the authorization code flow with the "user-read-email" scope.
|
|
@@ -6,7 +7,4 @@ import type { OAuthProvider } from "./interfaces";
|
|
|
6
7
|
export declare function createSpotifyProvider(config: {
|
|
7
8
|
clientId: string;
|
|
8
9
|
clientSecret: string;
|
|
9
|
-
}): OAuthProvider<
|
|
10
|
-
code: string;
|
|
11
|
-
redirectUri: string;
|
|
12
|
-
}>;
|
|
10
|
+
}): OAuthProvider<OAuthCodeFlowPayload>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OAuthProvider } from "./interfaces";
|
|
2
|
+
import { type OAuthCodeFlowPayload } from "./oauth-code-flow";
|
|
2
3
|
/**
|
|
3
4
|
* Creates a Twitter/X OAuth 2.0 Provider integration.
|
|
4
5
|
*
|
|
@@ -11,8 +12,4 @@ import type { OAuthProvider } from "./interfaces";
|
|
|
11
12
|
export declare function createTwitterProvider(config: {
|
|
12
13
|
clientId: string;
|
|
13
14
|
clientSecret: string;
|
|
14
|
-
}): OAuthProvider<
|
|
15
|
-
code: string;
|
|
16
|
-
redirectUri: string;
|
|
17
|
-
codeVerifier: string;
|
|
18
|
-
}>;
|
|
15
|
+
}): OAuthProvider<OAuthCodeFlowPayload>;
|