@fonoster/identity 0.26.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/allowList.d.ts +4 -0
- package/dist/allowList.js +7 -1
- package/dist/apikeys/createCreateApiKey.js +16 -0
- package/dist/apikeys/createDeleteApiKey.js +22 -2
- package/dist/apikeys/createListApiKeys.js +14 -0
- package/dist/apikeys/createRegenerateApiKey.js +23 -2
- package/dist/config/index.d.ts +20 -0
- package/dist/config/index.js +36 -0
- package/dist/config/publishedKeys.d.ts +9 -0
- package/dist/config/publishedKeys.js +56 -0
- package/dist/config/schemas.d.ts +392 -0
- package/dist/config/schemas.js +163 -0
- package/dist/config/toIdentityConfig.d.ts +19 -0
- package/dist/config/toIdentityConfig.js +93 -0
- package/dist/constants.d.ts +28 -0
- package/dist/constants.js +29 -1
- package/dist/exchanges/createExchangeApiKey.js +4 -1
- package/dist/exchanges/createExchangeCredentials.d.ts +6 -1
- package/dist/exchanges/createExchangeCredentials.js +20 -2
- package/dist/exchanges/createExchangeOauth2Code.js +25 -2
- package/dist/exchanges/createExchangeRefreshToken.d.ts +9 -1
- package/dist/exchanges/createExchangeRefreshToken.js +57 -13
- package/dist/exchanges/createExchangeVerificationCode.d.ts +35 -0
- package/dist/exchanges/createExchangeVerificationCode.js +95 -0
- package/dist/exchanges/createRevokeToken.d.ts +33 -0
- package/dist/exchanges/createRevokeToken.js +64 -0
- package/dist/exchanges/exchangeTokens.d.ts +3 -1
- package/dist/exchanges/exchangeTokens.js +23 -5
- package/dist/exchanges/index.d.ts +2 -0
- package/dist/exchanges/index.js +2 -0
- package/dist/exchanges/payloads/users/createGetIdTokenPayload.js +3 -2
- package/dist/exchanges/refreshTokens.d.ts +30 -0
- package/dist/exchanges/refreshTokens.js +101 -0
- package/dist/exchanges/types.d.ts +53 -2
- package/dist/generated/@prisma/client/edge.js +32 -9
- package/dist/generated/@prisma/client/index-browser.js +24 -1
- package/dist/generated/@prisma/client/index.d.ts +3233 -417
- package/dist/generated/@prisma/client/index.js +34 -11
- package/dist/generated/@prisma/client/{libquery_engine-darwin-arm64.dylib.node → libquery_engine-debian-openssl-3.0.x.so.node} +0 -0
- package/dist/generated/@prisma/client/package.json +1 -1
- package/dist/generated/@prisma/client/schema.prisma +47 -7
- package/dist/generated/@prisma/client/wasm.js +32 -9
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server/config.d.ts +286 -55
- package/dist/server/config.js +74 -108
- package/dist/server/index.js +5 -3
- package/dist/service.d.ts +6 -0
- package/dist/service.js +11 -4
- package/dist/users/createCreateUser.d.ts +6 -1
- package/dist/users/createCreateUser.js +17 -9
- package/dist/users/createCreateUserWithOauth2Code.js +19 -15
- package/dist/users/createResetPassword.d.ts +5 -0
- package/dist/users/createResetPassword.js +33 -7
- package/dist/users/createSendResetPasswordCode.d.ts +6 -0
- package/dist/users/createSendResetPasswordCode.js +33 -1
- package/dist/users/createUpdateUser.d.ts +7 -0
- package/dist/users/createUpdateUser.js +41 -8
- package/dist/users/createUserFromGitHub.d.ts +40 -0
- package/dist/users/createUserFromGitHub.js +27 -0
- package/dist/users/index.d.ts +1 -0
- package/dist/users/index.js +1 -0
- package/dist/users/migratePasswordStorage.d.ts +7 -0
- package/dist/users/migratePasswordStorage.js +47 -0
- package/dist/users/types.d.ts +3 -1
- package/dist/users/upsertDefaultUser.js +6 -14
- package/dist/utils/authMethods.d.ts +31 -0
- package/dist/utils/authMethods.js +44 -0
- package/dist/utils/claimUnverifiedAccount.d.ts +35 -0
- package/dist/utils/claimUnverifiedAccount.js +43 -0
- package/dist/utils/clientAddress.d.ts +26 -0
- package/dist/utils/clientAddress.js +9 -0
- package/dist/utils/createGenerateVerificationCode.d.ts +4 -0
- package/dist/utils/createGenerateVerificationCode.js +12 -2
- package/dist/utils/createGenerateWorkspaceInviteToken.js +3 -0
- package/dist/utils/createGetApiKeyByAccessKeyId.d.ts +5 -1
- package/dist/utils/createGetApiKeyByAccessKeyId.js +5 -0
- package/dist/utils/createGetUserByAccessKeyId.d.ts +3 -1
- package/dist/utils/createGetUserByEmail.d.ts +3 -1
- package/dist/utils/createGetUserByGitHubEmails.d.ts +41 -0
- package/dist/utils/createGetUserByGitHubEmails.js +31 -0
- package/dist/utils/createGetUserByWorkspaceAccessKeyId.d.ts +3 -1
- package/dist/utils/createIsValidVerificationCode.d.ts +5 -0
- package/dist/utils/createIsValidVerificationCode.js +34 -7
- package/dist/utils/createSendEmail.d.ts +2 -1
- package/dist/utils/createSendEmail.js +17 -0
- package/dist/utils/createUpdateMembershipStatus.js +10 -3
- package/dist/utils/generateOneTimePassword.d.ts +3 -0
- package/dist/utils/generateOneTimePassword.js +25 -0
- package/dist/utils/getGitHubUserWithOauth2Code.d.ts +20 -2
- package/dist/utils/getGitHubUserWithOauth2Code.js +41 -9
- package/dist/utils/isWorkspaceAdminCall.d.ts +8 -0
- package/dist/utils/isWorkspaceAdminCall.js +47 -0
- package/dist/utils/normalizeContact.d.ts +22 -0
- package/dist/utils/normalizeContact.js +28 -0
- package/dist/utils/passwords.d.ts +11 -0
- package/dist/utils/passwords.js +84 -0
- package/dist/utils/rateLimiter.d.ts +20 -0
- package/dist/utils/rateLimiter.js +108 -0
- package/dist/utils/signInThrottle.d.ts +47 -0
- package/dist/utils/signInThrottle.js +90 -0
- package/dist/utils/userPasswords.d.ts +25 -0
- package/dist/utils/userPasswords.js +98 -0
- package/dist/verification/createSendVerificationCode.js +27 -1
- package/dist/verification/createVerifyCode.js +10 -0
- package/dist/workspaces/createCreateWorkspace.d.ts +7 -1
- package/dist/workspaces/createCreateWorkspace.js +41 -5
- package/dist/workspaces/createDeleteWorkspace.d.ts +2 -1
- package/dist/workspaces/createDeleteWorkspace.js +16 -2
- package/dist/workspaces/createInviteUserToWorkspace.js +19 -17
- package/dist/workspaces/createResendWorkspaceMembershipInvitation.js +17 -2
- package/dist/workspaces/createUpdateWorkspace.js +18 -4
- package/dist/workspaces/createWorkspaceDirectory.d.ts +29 -0
- package/dist/workspaces/createWorkspaceDirectory.js +42 -0
- package/dist/workspaces/index.d.ts +1 -0
- package/dist/workspaces/index.js +1 -0
- package/package.json +7 -6
- package/dist/generated/@prisma/client/runtime/wasm.js +0 -35
package/dist/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.VERIFICATION_CODE_EXPIRATION = exports.SIGN_ALGORITHM = void 0;
|
|
3
|
+
exports.REFRESH_TOKEN_REUSE_GRACE_MS = exports.SIGN_IN_FAILURE_LIMITS = exports.RESET_PASSWORD_ATTEMPT_LIMITS = exports.VERIFICATION_CODE_SEND_LIMITS = exports.VERIFICATION_CODE_MAX_ATTEMPTS = exports.VERIFICATION_CODE_EXPIRATION = exports.SIGN_ALGORITHM = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
6
6
|
* http://github.com/fonoster/fonoster
|
|
@@ -21,3 +21,31 @@ exports.VERIFICATION_CODE_EXPIRATION = exports.SIGN_ALGORITHM = void 0;
|
|
|
21
21
|
*/
|
|
22
22
|
exports.SIGN_ALGORITHM = "RS256";
|
|
23
23
|
exports.VERIFICATION_CODE_EXPIRATION = 5 * 60 * 1000;
|
|
24
|
+
/** Wrong guesses allowed before a code stops working. */
|
|
25
|
+
exports.VERIFICATION_CODE_MAX_ATTEMPTS = 5;
|
|
26
|
+
/** Codes sent per email, and per client IP, within the window. */
|
|
27
|
+
exports.VERIFICATION_CODE_SEND_LIMITS = {
|
|
28
|
+
perEmail: 3,
|
|
29
|
+
perIp: 20,
|
|
30
|
+
windowMs: 10 * 60 * 1000
|
|
31
|
+
};
|
|
32
|
+
/** ResetPassword attempts per email, and per client IP, within the window. */
|
|
33
|
+
exports.RESET_PASSWORD_ATTEMPT_LIMITS = {
|
|
34
|
+
perEmail: 10,
|
|
35
|
+
perIp: 30,
|
|
36
|
+
windowMs: 10 * 60 * 1000
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Failed sign-ins (ExchangeCredentials, ExchangeVerificationCode) per email,
|
|
40
|
+
* and per client IP, before sign-in is refused for the rest of the window.
|
|
41
|
+
*/
|
|
42
|
+
exports.SIGN_IN_FAILURE_LIMITS = {
|
|
43
|
+
perEmail: 10,
|
|
44
|
+
perIp: 50,
|
|
45
|
+
windowMs: 15 * 60 * 1000
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* How long a just-used refresh token may be used again (concurrent renewals,
|
|
49
|
+
* e.g. two browser tabs). Later reuse revokes the token's family.
|
|
50
|
+
*/
|
|
51
|
+
exports.REFRESH_TOKEN_REUSE_GRACE_MS = 30 * 1000;
|
|
@@ -73,7 +73,10 @@ function createExchangeApiKey(prisma, identityConfig) {
|
|
|
73
73
|
const { accessKeyId, accessKeySecret } = request;
|
|
74
74
|
logger.verbose("call to exchangeApiKey", { accessKeyId });
|
|
75
75
|
const key = yield (0, createGetApiKeyByAccessKeyId_1.createGetApiKeyByAccessKeyId)(prisma)(accessKeyId);
|
|
76
|
-
|
|
76
|
+
// An expired key gets the same answer as a wrong secret.
|
|
77
|
+
if (!key ||
|
|
78
|
+
key.accessKeySecret !== (accessKeySecret === null || accessKeySecret === void 0 ? void 0 : accessKeySecret.trim()) ||
|
|
79
|
+
(0, createGetApiKeyByAccessKeyId_1.isApiKeyExpired)(key)) {
|
|
77
80
|
return callback({
|
|
78
81
|
code: grpc.status.PERMISSION_DENIED,
|
|
79
82
|
message: "Invalid credentials"
|
|
@@ -18,8 +18,13 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { GrpcErrorMessage } from "@fonoster/common";
|
|
20
20
|
import { Prisma } from "../db";
|
|
21
|
+
import { SignInThrottle } from "../utils/signInThrottle";
|
|
21
22
|
import { IdentityConfig } from "./types";
|
|
22
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Password sign-in. Failed attempts are throttled per email and per client IP
|
|
25
|
+
* (see createSignInThrottle).
|
|
26
|
+
*/
|
|
27
|
+
declare function createExchangeCredentials(prisma: Prisma, identityConfig: IdentityConfig, throttle?: SignInThrottle): (call: {
|
|
23
28
|
request: unknown;
|
|
24
29
|
}, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
|
|
25
30
|
export { createExchangeCredentials };
|
|
@@ -64,19 +64,36 @@ exports.createExchangeCredentials = createExchangeCredentials;
|
|
|
64
64
|
const common_1 = require("@fonoster/common");
|
|
65
65
|
const logger_1 = require("@fonoster/logger");
|
|
66
66
|
const grpc = __importStar(require("@grpc/grpc-js"));
|
|
67
|
+
const authMethods_1 = require("../utils/authMethods");
|
|
68
|
+
const clientAddress_1 = require("../utils/clientAddress");
|
|
67
69
|
const createGetUserByEmail_1 = require("../utils/createGetUserByEmail");
|
|
68
70
|
const createIsValidVerificationCode_1 = require("../utils/createIsValidVerificationCode");
|
|
71
|
+
const signInThrottle_1 = require("../utils/signInThrottle");
|
|
72
|
+
const userPasswords_1 = require("../utils/userPasswords");
|
|
69
73
|
const verification_1 = require("../verification");
|
|
70
74
|
const exchangeTokens_1 = require("./exchangeTokens");
|
|
71
75
|
const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filename });
|
|
72
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Password sign-in. Failed attempts are throttled per email and per client IP
|
|
78
|
+
* (see createSignInThrottle).
|
|
79
|
+
*/
|
|
80
|
+
function createExchangeCredentials(prisma, identityConfig, throttle = (0, signInThrottle_1.createSignInThrottle)()) {
|
|
73
81
|
const isValidVerificationCode = (0, createIsValidVerificationCode_1.createIsValidVerificationCode)(prisma);
|
|
82
|
+
const verifyUserPassword = (0, userPasswords_1.createVerifyUserPassword)(prisma);
|
|
74
83
|
const exchangeCredentials = (call, callback) => __awaiter(this, void 0, void 0, function* () {
|
|
75
84
|
const { request } = call;
|
|
76
85
|
const { username: email, password, twoFactorCode } = request;
|
|
77
86
|
logger.verbose("call to exchangeCredentials", { username: email });
|
|
87
|
+
if (!(0, authMethods_1.isMethodEnabled)(identityConfig, "password")) {
|
|
88
|
+
return callback((0, authMethods_1.methodDisabledError)("password"));
|
|
89
|
+
}
|
|
90
|
+
const ip = (0, clientAddress_1.clientAddress)(call);
|
|
91
|
+
if (throttle.isBlocked(email, ip)) {
|
|
92
|
+
return callback(signInThrottle_1.tooManySignInAttempts);
|
|
93
|
+
}
|
|
78
94
|
const user = yield (0, createGetUserByEmail_1.createGetUserByEmail)(prisma)(email);
|
|
79
|
-
if (!user || user
|
|
95
|
+
if (!user || !(yield verifyUserPassword(user, password === null || password === void 0 ? void 0 : password.trim()))) {
|
|
96
|
+
throttle.recordFailure(email, ip);
|
|
80
97
|
return callback({
|
|
81
98
|
code: grpc.status.PERMISSION_DENIED,
|
|
82
99
|
message: "Invalid credentials"
|
|
@@ -91,6 +108,7 @@ function createExchangeCredentials(prisma, identityConfig) {
|
|
|
91
108
|
code: twoFactorCode
|
|
92
109
|
});
|
|
93
110
|
if (!isValid) {
|
|
111
|
+
throttle.recordFailure(email, ip);
|
|
94
112
|
return callback({
|
|
95
113
|
code: grpc.status.PERMISSION_DENIED,
|
|
96
114
|
message: "Invalid 2FA code"
|
|
@@ -64,21 +64,44 @@ exports.createExchangeOauth2Code = createExchangeOauth2Code;
|
|
|
64
64
|
const common_1 = require("@fonoster/common");
|
|
65
65
|
const logger_1 = require("@fonoster/logger");
|
|
66
66
|
const grpc = __importStar(require("@grpc/grpc-js"));
|
|
67
|
-
const
|
|
67
|
+
const createUserFromGitHub_1 = require("../users/createUserFromGitHub");
|
|
68
|
+
const authMethods_1 = require("../utils/authMethods");
|
|
69
|
+
const claimUnverifiedAccount_1 = require("../utils/claimUnverifiedAccount");
|
|
70
|
+
const createGetUserByGitHubEmails_1 = require("../utils/createGetUserByGitHubEmails");
|
|
68
71
|
const getGitHubUserWithOauth2Code_1 = require("../utils/getGitHubUserWithOauth2Code");
|
|
69
72
|
const exchangeTokens_1 = require("./exchangeTokens");
|
|
70
73
|
const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filename });
|
|
71
74
|
function createExchangeOauth2Code(prisma, identityConfig) {
|
|
72
75
|
const exchangeOauth2Code = (call, callback) => __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
var _a, _b;
|
|
73
77
|
const { request } = call;
|
|
74
78
|
const { provider, code } = request;
|
|
75
79
|
logger.verbose("call to exchangeOauth2Code", { provider });
|
|
80
|
+
if (!(0, authMethods_1.isMethodEnabled)(identityConfig, "github") ||
|
|
81
|
+
!identityConfig.githubOauth2Config) {
|
|
82
|
+
return callback((0, authMethods_1.methodDisabledError)("github"));
|
|
83
|
+
}
|
|
76
84
|
const userData = yield (0, getGitHubUserWithOauth2Code_1.getGitHubUserWithOauth2Code)({
|
|
77
85
|
clientId: identityConfig.githubOauth2Config.clientId,
|
|
78
86
|
clientSecret: identityConfig.githubOauth2Config.clientSecret,
|
|
87
|
+
url: identityConfig.githubOauth2Config.url,
|
|
88
|
+
apiUrl: identityConfig.githubOauth2Config.apiUrl,
|
|
79
89
|
code
|
|
80
90
|
});
|
|
81
|
-
|
|
91
|
+
if (!userData.email) {
|
|
92
|
+
return callback({
|
|
93
|
+
code: grpc.status.PERMISSION_DENIED,
|
|
94
|
+
message: "Your GitHub account has no verified primary email"
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// First GitHub sign-in creates the account when sign-up is open; invited
|
|
98
|
+
// users already exist (the invite created them). GitHub verified the
|
|
99
|
+
// email, so an existing account whose email was never verified loses its
|
|
100
|
+
// password (M2).
|
|
101
|
+
const existing = yield (0, createGetUserByGitHubEmails_1.createGetUserByGitHubEmails)(prisma)((_a = userData.verifiedEmails) !== null && _a !== void 0 ? _a : [userData.email]);
|
|
102
|
+
const user = (_b = (existing && (yield (0, claimUnverifiedAccount_1.createClaimUnverifiedAccount)(prisma)(existing)))) !== null && _b !== void 0 ? _b : ((0, authMethods_1.isOpenSignup)(identityConfig)
|
|
103
|
+
? yield (0, createUserFromGitHub_1.createUserFromGitHub)(prisma)(Object.assign(Object.assign({}, userData), { email: userData.email }))
|
|
104
|
+
: null);
|
|
82
105
|
if (!user) {
|
|
83
106
|
return callback({
|
|
84
107
|
code: grpc.status.PERMISSION_DENIED,
|
|
@@ -19,7 +19,15 @@
|
|
|
19
19
|
import { GrpcErrorMessage } from "@fonoster/common";
|
|
20
20
|
import { Prisma } from "../db";
|
|
21
21
|
import { IdentityConfig } from "./types";
|
|
22
|
+
/**
|
|
23
|
+
* The claims of a refresh token this Identity issued (RS256, its issuer and
|
|
24
|
+
* audience, `tokenUse: "refresh"`, with a `jti`), or undefined.
|
|
25
|
+
*/
|
|
26
|
+
declare function verifyRefreshToken(token: string, identityConfig: IdentityConfig): {
|
|
27
|
+
jti: string;
|
|
28
|
+
accessKeyId: string;
|
|
29
|
+
};
|
|
22
30
|
declare function createExchangeRefreshToken(prisma: Prisma, identityConfig: IdentityConfig): (call: {
|
|
23
31
|
request: unknown;
|
|
24
32
|
}, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
|
|
25
|
-
export { createExchangeRefreshToken };
|
|
33
|
+
export { createExchangeRefreshToken, verifyRefreshToken };
|
|
@@ -8,11 +8,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
12
|
exports.createExchangeRefreshToken = createExchangeRefreshToken;
|
|
13
|
+
exports.verifyRefreshToken = verifyRefreshToken;
|
|
16
14
|
/**
|
|
17
15
|
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
18
16
|
* http://github.com/fonoster/fonoster
|
|
@@ -33,21 +31,67 @@ exports.createExchangeRefreshToken = createExchangeRefreshToken;
|
|
|
33
31
|
*/
|
|
34
32
|
const common_1 = require("@fonoster/common");
|
|
35
33
|
const logger_1 = require("@fonoster/logger");
|
|
36
|
-
const
|
|
37
|
-
const
|
|
34
|
+
const grpc_js_1 = require("@grpc/grpc-js");
|
|
35
|
+
const createGetApiKeyByAccessKeyId_1 = require("../utils/createGetApiKeyByAccessKeyId");
|
|
38
36
|
const exchangeTokens_1 = require("./exchangeTokens");
|
|
37
|
+
const refreshTokens_1 = require("./refreshTokens");
|
|
39
38
|
const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filename });
|
|
39
|
+
const invalidRefreshToken = {
|
|
40
|
+
code: grpc_js_1.status.UNAUTHENTICATED,
|
|
41
|
+
message: "Invalid refresh token"
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The claims of a refresh token this Identity issued (RS256, its issuer and
|
|
45
|
+
* audience, `tokenUse: "refresh"`, with a `jti`), or undefined.
|
|
46
|
+
*/
|
|
47
|
+
function verifyRefreshToken(token, identityConfig) {
|
|
48
|
+
const claims = (0, common_1.verifyToken)(token, identityConfig.publicKey, {
|
|
49
|
+
issuer: identityConfig.issuer,
|
|
50
|
+
audience: identityConfig.audience
|
|
51
|
+
});
|
|
52
|
+
if (!claims ||
|
|
53
|
+
claims.tokenUse !== common_1.TokenUseEnum.REFRESH ||
|
|
54
|
+
typeof claims.jti !== "string" ||
|
|
55
|
+
typeof claims.accessKeyId !== "string") {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return claims;
|
|
59
|
+
}
|
|
40
60
|
function createExchangeRefreshToken(prisma, identityConfig) {
|
|
61
|
+
const refreshTokens = (0, refreshTokens_1.createRefreshTokenStore)(prisma);
|
|
62
|
+
const getApiKey = (0, createGetApiKeyByAccessKeyId_1.createGetApiKeyByAccessKeyId)(prisma);
|
|
63
|
+
/** The user or API key still exists (and the key hasn't expired). */
|
|
64
|
+
const principalIsActive = (accessKeyId) => __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
if (accessKeyId.startsWith("US")) {
|
|
66
|
+
return (yield prisma.user.findFirst({ where: { accessKeyId } })) !== null;
|
|
67
|
+
}
|
|
68
|
+
const key = yield getApiKey(accessKeyId);
|
|
69
|
+
return key !== null && !(0, createGetApiKeyByAccessKeyId_1.isApiKeyExpired)(key);
|
|
70
|
+
});
|
|
41
71
|
const exchangeRefreshToken = (call, callback) => __awaiter(this, void 0, void 0, function* () {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
});
|
|
48
|
-
const { accessKeyId } = oldRefreshTokenDecoded;
|
|
72
|
+
const claims = verifyRefreshToken(call.request.refreshToken, identityConfig);
|
|
73
|
+
if (!claims) {
|
|
74
|
+
return callback(invalidRefreshToken);
|
|
75
|
+
}
|
|
76
|
+
const { jti, accessKeyId } = claims;
|
|
49
77
|
logger.verbose("call to exchangeRefreshToken", { accessKeyId });
|
|
50
|
-
|
|
78
|
+
// Single use: a token seen before (or revoked) revokes its whole family.
|
|
79
|
+
const consumed = yield refreshTokens.consume(jti);
|
|
80
|
+
if (consumed.status !== "ok" || consumed.accessKeyId !== accessKeyId) {
|
|
81
|
+
if (consumed.status === "reused") {
|
|
82
|
+
logger.warn("refresh token reused; its family is revoked", {
|
|
83
|
+
accessKeyId
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return callback(invalidRefreshToken);
|
|
87
|
+
}
|
|
88
|
+
if (!(yield principalIsActive(accessKeyId))) {
|
|
89
|
+
yield refreshTokens.revokeFamily(consumed.familyId);
|
|
90
|
+
return callback(invalidRefreshToken);
|
|
91
|
+
}
|
|
92
|
+
callback(null, yield (0, exchangeTokens_1.exchangeTokens)(prisma, identityConfig)(accessKeyId, {
|
|
93
|
+
familyId: consumed.familyId
|
|
94
|
+
}));
|
|
51
95
|
});
|
|
52
96
|
return (0, common_1.withErrorHandlingAndValidation)(exchangeRefreshToken, common_1.exchangeRefreshTokenRequestSchema);
|
|
53
97
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
3
|
+
* http://github.com/fonoster/fonoster
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Fonoster
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the MIT License (the "License");
|
|
8
|
+
* you may not use this file except in compliance with
|
|
9
|
+
* the License. You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* https://opensource.org/licenses/MIT
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { GrpcErrorMessage } from "@fonoster/common";
|
|
20
|
+
import { Prisma } from "../db";
|
|
21
|
+
import { SignInThrottle } from "../utils/signInThrottle";
|
|
22
|
+
import { IdentityConfig } from "./types";
|
|
23
|
+
type ExchangeVerificationCodeRequest = {
|
|
24
|
+
email: string;
|
|
25
|
+
code: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Passwordless sign-in: trades the code from SendVerificationCode for tokens.
|
|
29
|
+
* The first sign-in creates the account when sign-up is open; with invite-only
|
|
30
|
+
* sign-up, unknown emails get the same error as a wrong code.
|
|
31
|
+
*/
|
|
32
|
+
declare function createExchangeVerificationCode(prisma: Prisma, identityConfig: IdentityConfig, throttle?: SignInThrottle): (call: {
|
|
33
|
+
request: unknown;
|
|
34
|
+
}, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
|
|
35
|
+
export { createExchangeVerificationCode, ExchangeVerificationCodeRequest };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.createExchangeVerificationCode = createExchangeVerificationCode;
|
|
13
|
+
/**
|
|
14
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
15
|
+
* http://github.com/fonoster/fonoster
|
|
16
|
+
*
|
|
17
|
+
* This file is part of Fonoster
|
|
18
|
+
*
|
|
19
|
+
* Licensed under the MIT License (the "License");
|
|
20
|
+
* you may not use this file except in compliance with
|
|
21
|
+
* the License. You may obtain a copy of the License at
|
|
22
|
+
*
|
|
23
|
+
* https://opensource.org/licenses/MIT
|
|
24
|
+
*
|
|
25
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
26
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
27
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
28
|
+
* See the License for the specific language governing permissions and
|
|
29
|
+
* limitations under the License.
|
|
30
|
+
*/
|
|
31
|
+
const common_1 = require("@fonoster/common");
|
|
32
|
+
const logger_1 = require("@fonoster/logger");
|
|
33
|
+
const grpc_js_1 = require("@grpc/grpc-js");
|
|
34
|
+
const authMethods_1 = require("../utils/authMethods");
|
|
35
|
+
const claimUnverifiedAccount_1 = require("../utils/claimUnverifiedAccount");
|
|
36
|
+
const clientAddress_1 = require("../utils/clientAddress");
|
|
37
|
+
const createGetUserByEmail_1 = require("../utils/createGetUserByEmail");
|
|
38
|
+
const createIsValidVerificationCode_1 = require("../utils/createIsValidVerificationCode");
|
|
39
|
+
const generateAccessKeyId_1 = require("../utils/generateAccessKeyId");
|
|
40
|
+
const signInThrottle_1 = require("../utils/signInThrottle");
|
|
41
|
+
const types_1 = require("../verification/types");
|
|
42
|
+
const exchangeTokens_1 = require("./exchangeTokens");
|
|
43
|
+
const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filename });
|
|
44
|
+
const invalidCode = {
|
|
45
|
+
code: grpc_js_1.status.PERMISSION_DENIED,
|
|
46
|
+
message: "Invalid verification code"
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Passwordless sign-in: trades the code from SendVerificationCode for tokens.
|
|
50
|
+
* The first sign-in creates the account when sign-up is open; with invite-only
|
|
51
|
+
* sign-up, unknown emails get the same error as a wrong code.
|
|
52
|
+
*/
|
|
53
|
+
function createExchangeVerificationCode(prisma, identityConfig, throttle = (0, signInThrottle_1.createSignInThrottle)()) {
|
|
54
|
+
const isValidVerificationCode = (0, createIsValidVerificationCode_1.createIsValidVerificationCode)(prisma);
|
|
55
|
+
const getUserByEmail = (0, createGetUserByEmail_1.createGetUserByEmail)(prisma);
|
|
56
|
+
const claimUnverifiedAccount = (0, claimUnverifiedAccount_1.createClaimUnverifiedAccount)(prisma);
|
|
57
|
+
const exchangeVerificationCode = (call, callback) => __awaiter(this, void 0, void 0, function* () {
|
|
58
|
+
const email = call.request.email.trim().toLowerCase();
|
|
59
|
+
logger.verbose("call to exchangeVerificationCode", { email });
|
|
60
|
+
if (!(0, authMethods_1.isMethodEnabled)(identityConfig, "email_code")) {
|
|
61
|
+
return callback((0, authMethods_1.methodDisabledError)("email_code"));
|
|
62
|
+
}
|
|
63
|
+
const ip = (0, clientAddress_1.clientAddress)(call);
|
|
64
|
+
if (throttle.isBlocked(email, ip)) {
|
|
65
|
+
return callback(signInThrottle_1.tooManySignInAttempts);
|
|
66
|
+
}
|
|
67
|
+
const isValid = yield isValidVerificationCode({
|
|
68
|
+
type: types_1.ContactType.EMAIL,
|
|
69
|
+
value: email,
|
|
70
|
+
code: call.request.code
|
|
71
|
+
});
|
|
72
|
+
if (!isValid) {
|
|
73
|
+
throttle.recordFailure(email, ip);
|
|
74
|
+
return callback(invalidCode);
|
|
75
|
+
}
|
|
76
|
+
const existing = yield getUserByEmail(email);
|
|
77
|
+
if (!existing && !(0, authMethods_1.isOpenSignup)(identityConfig)) {
|
|
78
|
+
return callback(invalidCode);
|
|
79
|
+
}
|
|
80
|
+
// The code proves the address, so the email is verified either way. An
|
|
81
|
+
// account whose email was never verified loses its password (M2).
|
|
82
|
+
const user = existing
|
|
83
|
+
? yield claimUnverifiedAccount(existing)
|
|
84
|
+
: yield prisma.user.create({
|
|
85
|
+
data: {
|
|
86
|
+
name: email.split("@")[0].slice(0, 60),
|
|
87
|
+
email,
|
|
88
|
+
emailVerified: true,
|
|
89
|
+
accessKeyId: (0, generateAccessKeyId_1.generateAccessKeyId)(generateAccessKeyId_1.AccessKeyIdType.USER)
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
callback(null, yield (0, exchangeTokens_1.exchangeTokens)(prisma, identityConfig)(user.accessKeyId));
|
|
93
|
+
});
|
|
94
|
+
return (0, common_1.withErrorHandlingAndValidation)(exchangeVerificationCode, common_1.Validators.exchangeVerificationCodeRequestSchema);
|
|
95
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
3
|
+
* http://github.com/fonoster/fonoster
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Fonoster
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the MIT License (the "License");
|
|
8
|
+
* you may not use this file except in compliance with
|
|
9
|
+
* the License. You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* https://opensource.org/licenses/MIT
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { GrpcErrorMessage } from "@fonoster/common";
|
|
20
|
+
import { Prisma } from "../db";
|
|
21
|
+
import { IdentityConfig } from "./types";
|
|
22
|
+
type RevokeTokenRequest = {
|
|
23
|
+
token: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Sign-out: revokes the refresh token and every token rotated from the same
|
|
27
|
+
* sign-in. Holding the refresh token is the authorization, so the method is
|
|
28
|
+
* public. Access tokens already issued stay valid until they expire.
|
|
29
|
+
*/
|
|
30
|
+
declare function createRevokeToken(prisma: Prisma, identityConfig: IdentityConfig): (call: {
|
|
31
|
+
request: unknown;
|
|
32
|
+
}, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
|
|
33
|
+
export { createRevokeToken, RevokeTokenRequest };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.createRevokeToken = createRevokeToken;
|
|
13
|
+
/**
|
|
14
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
15
|
+
* http://github.com/fonoster/fonoster
|
|
16
|
+
*
|
|
17
|
+
* This file is part of Fonoster
|
|
18
|
+
*
|
|
19
|
+
* Licensed under the MIT License (the "License");
|
|
20
|
+
* you may not use this file except in compliance with
|
|
21
|
+
* the License. You may obtain a copy of the License at
|
|
22
|
+
*
|
|
23
|
+
* https://opensource.org/licenses/MIT
|
|
24
|
+
*
|
|
25
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
26
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
27
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
28
|
+
* See the License for the specific language governing permissions and
|
|
29
|
+
* limitations under the License.
|
|
30
|
+
*/
|
|
31
|
+
const common_1 = require("@fonoster/common");
|
|
32
|
+
const logger_1 = require("@fonoster/logger");
|
|
33
|
+
const grpc_js_1 = require("@grpc/grpc-js");
|
|
34
|
+
const createExchangeRefreshToken_1 = require("./createExchangeRefreshToken");
|
|
35
|
+
const refreshTokens_1 = require("./refreshTokens");
|
|
36
|
+
const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filename });
|
|
37
|
+
/**
|
|
38
|
+
* Sign-out: revokes the refresh token and every token rotated from the same
|
|
39
|
+
* sign-in. Holding the refresh token is the authorization, so the method is
|
|
40
|
+
* public. Access tokens already issued stay valid until they expire.
|
|
41
|
+
*/
|
|
42
|
+
function createRevokeToken(prisma, identityConfig) {
|
|
43
|
+
const refreshTokens = (0, refreshTokens_1.createRefreshTokenStore)(prisma);
|
|
44
|
+
const revokeToken = (call, callback) => __awaiter(this, void 0, void 0, function* () {
|
|
45
|
+
const claims = (0, createExchangeRefreshToken_1.verifyRefreshToken)(call.request.token, identityConfig);
|
|
46
|
+
if (!claims) {
|
|
47
|
+
return callback({
|
|
48
|
+
code: grpc_js_1.status.UNAUTHENTICATED,
|
|
49
|
+
message: "Invalid refresh token"
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const row = yield prisma.refreshToken.findUnique({
|
|
53
|
+
where: { jti: claims.jti }
|
|
54
|
+
});
|
|
55
|
+
if (row) {
|
|
56
|
+
yield refreshTokens.revokeFamily(row.familyId);
|
|
57
|
+
}
|
|
58
|
+
logger.verbose("refresh token revoked", {
|
|
59
|
+
accessKeyId: claims.accessKeyId
|
|
60
|
+
});
|
|
61
|
+
callback(null, { token: claims.jti });
|
|
62
|
+
});
|
|
63
|
+
return (0, common_1.withErrorHandlingAndValidation)(revokeToken, common_1.Validators.revokeTokenRequestSchema);
|
|
64
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Prisma } from "../db";
|
|
2
2
|
import { IdentityConfig } from "./types";
|
|
3
|
-
declare function exchangeTokens(prisma: Prisma, identityConfig: IdentityConfig): (accessKeyId: string
|
|
3
|
+
declare function exchangeTokens(prisma: Prisma, identityConfig: IdentityConfig): (accessKeyId: string, options?: {
|
|
4
|
+
familyId?: string;
|
|
5
|
+
}) => Promise<{
|
|
4
6
|
idToken: any;
|
|
5
7
|
accessToken: any;
|
|
6
8
|
refreshToken: any;
|
|
@@ -64,17 +64,28 @@ exports.exchangeTokens = exchangeTokens;
|
|
|
64
64
|
* See the License for the specific language governing permissions and
|
|
65
65
|
* limitations under the License.
|
|
66
66
|
*/
|
|
67
|
+
const crypto_1 = require("crypto");
|
|
67
68
|
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
69
|
+
const constants_1 = require("../constants");
|
|
68
70
|
const AK = __importStar(require("./payloads/apikeys"));
|
|
69
71
|
const US = __importStar(require("./payloads/users"));
|
|
70
|
-
const
|
|
72
|
+
const refreshTokens_1 = require("./refreshTokens");
|
|
71
73
|
// prettier-ignore
|
|
72
74
|
function exchangeTokens(prisma, identityConfig) {
|
|
73
|
-
|
|
75
|
+
const refreshTokens = (0, refreshTokens_1.createRefreshTokenStore)(prisma);
|
|
76
|
+
/**
|
|
77
|
+
* Issues tokens for `accessKeyId`. The refresh token gets a fresh `jti`,
|
|
78
|
+
* recorded so it can be used once; `familyId` continues a rotation chain
|
|
79
|
+
* (absent for a new sign-in).
|
|
80
|
+
*/
|
|
81
|
+
return (accessKeyId_1, ...args_1) => __awaiter(this, [accessKeyId_1, ...args_1], void 0, function* (accessKeyId, options = {}) {
|
|
82
|
+
var _a;
|
|
74
83
|
const { privateKey, idTokenExpiresIn, accessTokenExpiresIn, refreshTokenExpiresIn } = identityConfig;
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const
|
|
84
|
+
const jti = (0, crypto_1.randomUUID)();
|
|
85
|
+
const familyId = (_a = options.familyId) !== null && _a !== void 0 ? _a : (0, crypto_1.randomUUID)();
|
|
86
|
+
const idTokenSignOptions = { algorithm: constants_1.SIGN_ALGORITHM, expiresIn: idTokenExpiresIn };
|
|
87
|
+
const accessTokenSignOptions = { algorithm: constants_1.SIGN_ALGORITHM, expiresIn: accessTokenExpiresIn };
|
|
88
|
+
const refreshTokenSignOptions = { algorithm: constants_1.SIGN_ALGORITHM, expiresIn: refreshTokenExpiresIn, jwtid: jti };
|
|
78
89
|
let idToken = null;
|
|
79
90
|
let accessToken = null;
|
|
80
91
|
let refreshToken = null;
|
|
@@ -92,6 +103,13 @@ function exchangeTokens(prisma, identityConfig) {
|
|
|
92
103
|
accessToken = jsonwebtoken_1.default.sign(accessTokenPayload, privateKey, accessTokenSignOptions);
|
|
93
104
|
refreshToken = jsonwebtoken_1.default.sign(refreshTokenPayload, privateKey, refreshTokenSignOptions);
|
|
94
105
|
}
|
|
106
|
+
const { exp } = jsonwebtoken_1.default.decode(refreshToken);
|
|
107
|
+
yield refreshTokens.record({
|
|
108
|
+
jti,
|
|
109
|
+
familyId,
|
|
110
|
+
accessKeyId,
|
|
111
|
+
expiresAt: new Date(exp * 1000)
|
|
112
|
+
});
|
|
95
113
|
return {
|
|
96
114
|
idToken,
|
|
97
115
|
accessToken,
|
|
@@ -20,4 +20,6 @@ export * from "./createExchangeApiKey";
|
|
|
20
20
|
export * from "./createExchangeCredentials";
|
|
21
21
|
export * from "./createExchangeOauth2Code";
|
|
22
22
|
export * from "./createExchangeRefreshToken";
|
|
23
|
+
export * from "./createExchangeVerificationCode";
|
|
24
|
+
export * from "./createRevokeToken";
|
|
23
25
|
export * from "./types";
|
package/dist/exchanges/index.js
CHANGED
|
@@ -36,4 +36,6 @@ __exportStar(require("./createExchangeApiKey"), exports);
|
|
|
36
36
|
__exportStar(require("./createExchangeCredentials"), exports);
|
|
37
37
|
__exportStar(require("./createExchangeOauth2Code"), exports);
|
|
38
38
|
__exportStar(require("./createExchangeRefreshToken"), exports);
|
|
39
|
+
__exportStar(require("./createExchangeVerificationCode"), exports);
|
|
40
|
+
__exportStar(require("./createRevokeToken"), exports);
|
|
39
41
|
__exportStar(require("./types"), exports);
|
|
@@ -41,7 +41,7 @@ function createGetIdTokenPayload(prisma, identityConfig) {
|
|
|
41
41
|
return null;
|
|
42
42
|
}
|
|
43
43
|
const { issuer, audience } = identityConfig;
|
|
44
|
-
const { ref, email, phoneNumber, emailVerified, phoneNumberVerified } = user;
|
|
44
|
+
const { ref, email, phoneNumber, emailVerified, phoneNumberVerified, mustChangePassword } = user;
|
|
45
45
|
return {
|
|
46
46
|
iss: issuer,
|
|
47
47
|
sub: ref,
|
|
@@ -51,7 +51,8 @@ function createGetIdTokenPayload(prisma, identityConfig) {
|
|
|
51
51
|
email,
|
|
52
52
|
emailVerified,
|
|
53
53
|
phoneNumber,
|
|
54
|
-
phoneNumberVerified
|
|
54
|
+
phoneNumberVerified,
|
|
55
|
+
mustChangePassword
|
|
55
56
|
};
|
|
56
57
|
});
|
|
57
58
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Prisma } from "../db";
|
|
2
|
+
type ConsumeResult = {
|
|
3
|
+
status: "ok";
|
|
4
|
+
familyId: string;
|
|
5
|
+
accessKeyId: string;
|
|
6
|
+
} | {
|
|
7
|
+
status: "reused" | "unknown";
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Issued refresh tokens, by `jti`. A token is good for one exchange: using it
|
|
11
|
+
* marks it used and the exchange issues the next token in the same family. A
|
|
12
|
+
* token presented again (or after revocation) revokes the whole family, so a
|
|
13
|
+
* stolen token stops working for the thief and the owner alike. A token used
|
|
14
|
+
* moments ago (REFRESH_TOKEN_REUSE_GRACE_MS) may be used again, so two tabs
|
|
15
|
+
* renewing the same session at once don't sign each other out.
|
|
16
|
+
*/
|
|
17
|
+
declare function createRefreshTokenStore(prisma: Prisma): {
|
|
18
|
+
record(token: {
|
|
19
|
+
jti: string;
|
|
20
|
+
familyId: string;
|
|
21
|
+
accessKeyId: string;
|
|
22
|
+
expiresAt: Date;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
/** Marks `jti` used, once. A second use revokes its family. */
|
|
25
|
+
consume(jti: string): Promise<ConsumeResult>;
|
|
26
|
+
revokeFamily(familyId: string): Promise<void>;
|
|
27
|
+
/** Signs a user or API key out everywhere (password reset, key rotation). */
|
|
28
|
+
revokeAll(accessKeyId: string): Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
export { createRefreshTokenStore };
|