@fonoster/identity 0.27.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 +27 -4
- 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 +27 -4
- 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 +27 -4
- 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/libquery_engine-darwin-arm64.dylib.node +0 -0
- package/dist/generated/@prisma/client/runtime/wasm.js +0 -35
package/dist/server/config.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.identityServiceConfigSchema = void 0;
|
|
4
4
|
exports.loadConfig = loadConfig;
|
|
5
|
+
exports.loadConfigFile = loadConfigFile;
|
|
5
6
|
/**
|
|
6
7
|
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
7
8
|
* http://github.com/fonoster/fonoster
|
|
@@ -20,135 +21,100 @@ exports.loadConfig = loadConfig;
|
|
|
20
21
|
* See the License for the specific language governing permissions and
|
|
21
22
|
* limitations under the License.
|
|
22
23
|
*/
|
|
23
|
-
const
|
|
24
|
-
const path_1 = require("path");
|
|
24
|
+
const common_1 = require("@fonoster/common");
|
|
25
25
|
const zod_1 = require("zod");
|
|
26
|
-
const
|
|
26
|
+
const config_1 = require("../config");
|
|
27
27
|
/**
|
|
28
|
-
* Configuration for the standalone Identity service.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
28
|
+
* Configuration for the standalone Identity service: identity.json (or
|
|
29
|
+
* .jsonc/.yaml), found via --config, /opt/fonoster or ./config. Fields sit at
|
|
30
|
+
* the root of the file, as they always have; the same settings live under
|
|
31
|
+
* fonoster.yaml's `identity:` section when Identity is embedded in the API
|
|
32
|
+
* server. No environment variables, except ${VAR} in the database URL shared
|
|
33
|
+
* with the Postgres container.
|
|
34
34
|
*/
|
|
35
|
-
const keysSchema = zod_1.z
|
|
36
|
-
.object({
|
|
37
|
-
privateKey: zod_1.z.string().optional(),
|
|
38
|
-
publicKey: zod_1.z.string().optional(),
|
|
39
|
-
privateKeyPath: zod_1.z.string().optional(),
|
|
40
|
-
publicKeyPath: zod_1.z.string().optional()
|
|
41
|
-
})
|
|
42
|
-
.refine((k) => (k.privateKey || k.privateKeyPath) && (k.publicKey || k.publicKeyPath), {
|
|
43
|
-
message: "keys must provide a private and public key, inline or by path"
|
|
44
|
-
});
|
|
45
|
-
const smtpSchema = zod_1.z.object({
|
|
46
|
-
host: zod_1.z.string(),
|
|
47
|
-
port: zod_1.z.number(),
|
|
48
|
-
secure: zod_1.z.boolean().default(false),
|
|
49
|
-
sender: zod_1.z.string(),
|
|
50
|
-
auth: zod_1.z
|
|
51
|
-
.object({ user: zod_1.z.string().default(""), pass: zod_1.z.string().default("") })
|
|
52
|
-
.default({})
|
|
53
|
-
});
|
|
54
35
|
const identityServiceConfigSchema = zod_1.z.object({
|
|
55
36
|
server: zod_1.z
|
|
56
37
|
.object({
|
|
57
38
|
bindAddr: zod_1.z.string().default("0.0.0.0:50051"),
|
|
58
|
-
httpBridgePort: zod_1.z.number().default(9000)
|
|
39
|
+
httpBridgePort: zod_1.z.number().int().positive().default(9000)
|
|
59
40
|
})
|
|
60
41
|
.default({}),
|
|
42
|
+
logs: common_1.logsSchema,
|
|
61
43
|
database: zod_1.z.object({ url: zod_1.z.string().min(1) }),
|
|
62
|
-
encryptionKey:
|
|
44
|
+
encryptionKey: config_1.encryptionKeySchema,
|
|
63
45
|
issuer: zod_1.z.string().min(1),
|
|
64
|
-
audience: zod_1.z.string().min(1),
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
.
|
|
73
|
-
|
|
74
|
-
.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
})
|
|
78
|
-
.default({}),
|
|
79
|
-
invite: zod_1.z
|
|
80
|
-
.object({
|
|
81
|
-
url: zod_1.z.string().default(""),
|
|
82
|
-
failUrl: zod_1.z.string().default(""),
|
|
83
|
-
expiration: zod_1.z.string().default("2d")
|
|
84
|
-
})
|
|
85
|
-
.default({}),
|
|
86
|
-
appUrl: zod_1.z.string().default(""),
|
|
87
|
-
smtp: smtpSchema.optional(),
|
|
88
|
-
oauth2: zod_1.z
|
|
89
|
-
.object({
|
|
90
|
-
github: zod_1.z.object({ clientId: zod_1.z.string(), clientSecret: zod_1.z.string() })
|
|
46
|
+
audience: zod_1.z.string().min(1).default("api"),
|
|
47
|
+
/** Where the accept-invite bridge redirects. Defaults to the issuer. */
|
|
48
|
+
appUrl: zod_1.z.string().min(1).optional(),
|
|
49
|
+
keys: config_1.keysSchema,
|
|
50
|
+
tokens: config_1.tokensSchema,
|
|
51
|
+
security: config_1.securitySchema,
|
|
52
|
+
invite: config_1.invitesSchema,
|
|
53
|
+
smtp: config_1.smtpSchema
|
|
54
|
+
.omit({ user: true, password: true })
|
|
55
|
+
.extend({
|
|
56
|
+
auth: zod_1.z
|
|
57
|
+
.object({ user: zod_1.z.string().default(""), pass: zod_1.z.string().default("") })
|
|
58
|
+
.default({})
|
|
91
59
|
})
|
|
92
60
|
.optional(),
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
})
|
|
99
|
-
.optional()
|
|
61
|
+
twilio: config_1.twilioSchema.optional(),
|
|
62
|
+
oauth2: config_1.oauth2Schema,
|
|
63
|
+
/** Sign-in methods and sign-up policy (`features.auth` in fonoster.yaml). */
|
|
64
|
+
auth: config_1.authFeaturesSchema,
|
|
65
|
+
defaultUser: config_1.ownerSchema
|
|
100
66
|
});
|
|
101
67
|
exports.identityServiceConfigSchema = identityServiceConfigSchema;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
var _a;
|
|
113
|
-
return {
|
|
114
|
-
dbUrl: c.database.url,
|
|
115
|
-
issuer: c.issuer,
|
|
116
|
-
audience: c.audience,
|
|
117
|
-
privateKey: readKey(c.keys.privateKey, c.keys.privateKeyPath),
|
|
118
|
-
publicKey: readKey(c.keys.publicKey, c.keys.publicKeyPath),
|
|
119
|
-
encryptionKey: c.encryptionKey,
|
|
120
|
-
accessTokenExpiresIn: c.tokens.accessTokenExpiresIn,
|
|
121
|
-
refreshTokenExpiresIn: c.tokens.refreshTokenExpiresIn,
|
|
122
|
-
idTokenExpiresIn: c.tokens.idTokenExpiresIn,
|
|
123
|
-
workspaceInviteExpiration: c.invite.expiration,
|
|
124
|
-
workspaceInviteUrl: c.invite.url,
|
|
125
|
-
workspaceInviteFailUrl: c.invite.failUrl,
|
|
126
|
-
contactVerificationRequired: c.security.contactVerificationRequired,
|
|
127
|
-
twoFactorAuthenticationRequired: c.security.twoFactorAuthenticationRequired,
|
|
128
|
-
smtpConfig: c.smtp,
|
|
129
|
-
githubOauth2Config: (_a = c.oauth2) === null || _a === void 0 ? void 0 : _a.github
|
|
130
|
-
};
|
|
68
|
+
/** Reads and validates the file only (no keys read), e.g. for provisioning. */
|
|
69
|
+
function loadConfigFile(argv = process.argv) {
|
|
70
|
+
const config = (0, common_1.loadServiceConfig)({
|
|
71
|
+
service: "identity",
|
|
72
|
+
schema: identityServiceConfigSchema,
|
|
73
|
+
argv,
|
|
74
|
+
interpolatePaths: ["*"],
|
|
75
|
+
legacyEnvPrefixes: ["APISERVER_IDENTITY_"]
|
|
76
|
+
});
|
|
77
|
+
return { config, databaseUrl: config.database.url };
|
|
131
78
|
}
|
|
132
79
|
/** Loads and validates the service configuration from the file, failing fast. */
|
|
133
80
|
function loadConfig(argv = process.argv) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
catch (e) {
|
|
140
|
-
throw new Error(`unable to read Identity config at ${path}: ${e.message}`);
|
|
141
|
-
}
|
|
142
|
-
const result = identityServiceConfigSchema.safeParse(raw);
|
|
143
|
-
if (!result.success) {
|
|
144
|
-
throw new Error(`invalid Identity config at ${path}: ${(0, zod_validation_error_1.fromError)(result.error).toString()}`);
|
|
145
|
-
}
|
|
146
|
-
const config = result.data;
|
|
81
|
+
var _a;
|
|
82
|
+
const { config, databaseUrl } = loadConfigFile(argv);
|
|
83
|
+
const appUrl = (_a = config.appUrl) !== null && _a !== void 0 ? _a : config.issuer;
|
|
84
|
+
const smtp = config.smtp;
|
|
147
85
|
return {
|
|
148
86
|
bindAddr: config.server.bindAddr,
|
|
149
87
|
httpBridgePort: config.server.httpBridgePort,
|
|
150
|
-
appUrl
|
|
88
|
+
appUrl,
|
|
89
|
+
logs: config.logs,
|
|
90
|
+
databaseUrl,
|
|
151
91
|
defaultUser: config.defaultUser,
|
|
152
|
-
identityConfig: toIdentityConfig(
|
|
92
|
+
identityConfig: (0, config_1.toIdentityConfig)({
|
|
93
|
+
identity: {
|
|
94
|
+
issuer: config.issuer,
|
|
95
|
+
audience: config.audience,
|
|
96
|
+
keys: config.keys,
|
|
97
|
+
tokens: config.tokens,
|
|
98
|
+
security: config.security,
|
|
99
|
+
invites: config.invite,
|
|
100
|
+
oauth2: config.oauth2,
|
|
101
|
+
owner: config.defaultUser
|
|
102
|
+
},
|
|
103
|
+
appUrl,
|
|
104
|
+
databaseUrl,
|
|
105
|
+
encryptionKey: config.encryptionKey,
|
|
106
|
+
smtp: smtp
|
|
107
|
+
? {
|
|
108
|
+
host: smtp.host,
|
|
109
|
+
port: smtp.port,
|
|
110
|
+
secure: smtp.secure,
|
|
111
|
+
sender: smtp.sender,
|
|
112
|
+
user: smtp.auth.user,
|
|
113
|
+
password: smtp.auth.pass
|
|
114
|
+
}
|
|
115
|
+
: undefined,
|
|
116
|
+
twilio: config.twilio,
|
|
117
|
+
auth: config.auth
|
|
118
|
+
})
|
|
153
119
|
};
|
|
154
120
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -72,18 +72,20 @@ const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filena
|
|
|
72
72
|
* Standalone Identity gRPC service. Wraps `buildIdentityService` with the auth
|
|
73
73
|
* interceptor, the identity allow-list, a health service, and the accept-invite
|
|
74
74
|
* HTTP bridge — without any telephony subsystem. Configured entirely from a
|
|
75
|
-
* file (see `./config`)
|
|
75
|
+
* file (see `./config`): identity.json, fields at the root.
|
|
76
76
|
*/
|
|
77
77
|
function main() {
|
|
78
78
|
return __awaiter(this, void 0, void 0, function* () {
|
|
79
|
-
const { bindAddr, httpBridgePort, appUrl, defaultUser, identityConfig } = (0, config_1.loadConfig)();
|
|
79
|
+
const { bindAddr, httpBridgePort, appUrl, logs, defaultUser, identityConfig } = (0, config_1.loadConfig)();
|
|
80
|
+
(0, logger_1.configureLogger)(logs);
|
|
80
81
|
const { definition, handlers } = (0, __1.buildIdentityService)(identityConfig);
|
|
81
|
-
const authorization = (0, common_1.createAuthInterceptor)(identityConfig.publicKey, __1.identityAllowList);
|
|
82
|
+
const authorization = (0, common_1.createAuthInterceptor)(identityConfig.publicKey, __1.identityAllowList, { issuer: identityConfig.issuer, audience: identityConfig.audience });
|
|
82
83
|
const credentials = yield (0, common_1.getServerCredentials)({});
|
|
83
84
|
const healthImpl = new grpc_health_check_1.HealthImplementation(common_1.statusMap);
|
|
84
85
|
const server = new grpc.Server({ interceptors: [authorization] });
|
|
85
86
|
healthImpl.addToServer(server);
|
|
86
87
|
server.addService((0, common_1.createServiceDefinition)(definition), handlers);
|
|
88
|
+
yield (0, __1.migratePasswordStorage)(identityConfig);
|
|
87
89
|
if (defaultUser) {
|
|
88
90
|
yield (0, __1.upsertDefaultUser)(identityConfig, defaultUser);
|
|
89
91
|
}
|
package/dist/service.d.ts
CHANGED
|
@@ -86,6 +86,12 @@ declare function buildIdentityService(identityConfig: IdentityConfig): {
|
|
|
86
86
|
exchangeRefreshToken: (call: {
|
|
87
87
|
request: unknown;
|
|
88
88
|
}, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
|
|
89
|
+
exchangeVerificationCode: (call: {
|
|
90
|
+
request: unknown;
|
|
91
|
+
}, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
|
|
92
|
+
revokeToken: (call: {
|
|
93
|
+
request: unknown;
|
|
94
|
+
}, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
|
|
89
95
|
getPublicKey: (_: unknown, callback: (error: import("@fonoster/common").GrpcErrorMessage, response?: {
|
|
90
96
|
publicKey: string;
|
|
91
97
|
}) => void) => Promise<void>;
|
package/dist/service.js
CHANGED
|
@@ -23,6 +23,7 @@ exports.buildIdentityService = buildIdentityService;
|
|
|
23
23
|
const db_1 = require("./db");
|
|
24
24
|
const createExchangeOauth2Code_1 = require("./exchanges/createExchangeOauth2Code");
|
|
25
25
|
const getPublicKey_1 = require("./getPublicKey");
|
|
26
|
+
const authMethods_1 = require("./utils/authMethods");
|
|
26
27
|
const verification_1 = require("./verification");
|
|
27
28
|
const _1 = require(".");
|
|
28
29
|
const serviceDefinitionParams = {
|
|
@@ -33,13 +34,16 @@ const serviceDefinitionParams = {
|
|
|
33
34
|
};
|
|
34
35
|
exports.serviceDefinitionParams = serviceDefinitionParams;
|
|
35
36
|
function buildIdentityService(identityConfig) {
|
|
37
|
+
var _a, _b;
|
|
36
38
|
const prisma = (0, db_1.createPrismaClient)(identityConfig.dbUrl, identityConfig.encryptionKey);
|
|
39
|
+
// The control plane's regions read workspaces through this (add-private-regions PR-14).
|
|
40
|
+
(_b = (_a = identityConfig.regions) === null || _a === void 0 ? void 0 : _a.attachDirectory) === null || _b === void 0 ? void 0 : _b.call(_a, (0, _1.createWorkspaceDirectory)(prisma));
|
|
37
41
|
const service = {
|
|
38
42
|
definition: serviceDefinitionParams,
|
|
39
43
|
handlers: {
|
|
40
44
|
// Workspace operations
|
|
41
|
-
createWorkspace: (0, _1.createCreateWorkspace)(prisma),
|
|
42
|
-
deleteWorkspace: (0, _1.createDeleteWorkspace)(prisma),
|
|
45
|
+
createWorkspace: (0, _1.createCreateWorkspace)(prisma, identityConfig.regions),
|
|
46
|
+
deleteWorkspace: (0, _1.createDeleteWorkspace)(prisma, identityConfig.regions),
|
|
43
47
|
getWorkspace: (0, _1.createGetWorkspace)(prisma),
|
|
44
48
|
updateWorkspace: (0, _1.createUpdateWorkspace)(prisma),
|
|
45
49
|
listWorkspaces: (0, _1.createListWorkspaces)(prisma),
|
|
@@ -48,7 +52,7 @@ function buildIdentityService(identityConfig) {
|
|
|
48
52
|
resendWorkspaceMembershipInvitation: (0, _1.createResendWorkspaceMembershipInvitation)(prisma, identityConfig, _1.sendInvite),
|
|
49
53
|
removeUserFromWorkspace: (0, _1.createRemoveUserFromWorkspace)(prisma),
|
|
50
54
|
// User operations
|
|
51
|
-
createUser: (0, _1.createCreateUser)(prisma),
|
|
55
|
+
createUser: (0, _1.createCreateUser)(prisma, identityConfig),
|
|
52
56
|
createUserWithOauth2Code: (0, _1.createCreateUserWithOauth2Code)(prisma, identityConfig),
|
|
53
57
|
getUser: (0, _1.createGetUser)(prisma),
|
|
54
58
|
deleteUser: (0, _1.createDeleteUser)(prisma),
|
|
@@ -65,6 +69,8 @@ function buildIdentityService(identityConfig) {
|
|
|
65
69
|
exchangeCredentials: (0, _1.createExchangeCredentials)(prisma, identityConfig),
|
|
66
70
|
exchangeOauth2Code: (0, createExchangeOauth2Code_1.createExchangeOauth2Code)(prisma, identityConfig),
|
|
67
71
|
exchangeRefreshToken: (0, _1.createExchangeRefreshToken)(prisma, identityConfig),
|
|
72
|
+
exchangeVerificationCode: (0, _1.createExchangeVerificationCode)(prisma, identityConfig),
|
|
73
|
+
revokeToken: (0, _1.createRevokeToken)(prisma, identityConfig),
|
|
68
74
|
getPublicKey: (0, getPublicKey_1.createGetPublicKey)(identityConfig.publicKey),
|
|
69
75
|
// Placeholders for conditional handlers
|
|
70
76
|
sendVerificationCode: undefined,
|
|
@@ -72,7 +78,8 @@ function buildIdentityService(identityConfig) {
|
|
|
72
78
|
}
|
|
73
79
|
};
|
|
74
80
|
if (identityConfig.contactVerificationRequired ||
|
|
75
|
-
identityConfig.twoFactorAuthenticationRequired
|
|
81
|
+
identityConfig.twoFactorAuthenticationRequired ||
|
|
82
|
+
(0, authMethods_1.isMethodEnabled)(identityConfig, "email_code")) {
|
|
76
83
|
service.handlers.sendVerificationCode = (0, verification_1.createSendVerificationCode)(prisma, identityConfig);
|
|
77
84
|
service.handlers.verifyCode = (0, verification_1.createVerifyCode)(prisma);
|
|
78
85
|
}
|
|
@@ -18,7 +18,12 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { GrpcErrorMessage } from "@fonoster/common";
|
|
20
20
|
import { Prisma } from "../db";
|
|
21
|
-
|
|
21
|
+
import { IdentityConfig } from "../exchanges/types";
|
|
22
|
+
/**
|
|
23
|
+
* Self-service sign-up with a password. Allowed only when sign-up is open and
|
|
24
|
+
* password sign-in is enabled; invited users are created by the invite.
|
|
25
|
+
*/
|
|
26
|
+
declare function createCreateUser(prisma: Prisma, identityConfig: IdentityConfig): (call: {
|
|
22
27
|
request: unknown;
|
|
23
28
|
}, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
|
|
24
29
|
export { createCreateUser };
|
|
@@ -30,22 +30,30 @@ exports.createCreateUser = createCreateUser;
|
|
|
30
30
|
*/
|
|
31
31
|
const common_1 = require("@fonoster/common");
|
|
32
32
|
const logger_1 = require("@fonoster/logger");
|
|
33
|
+
const grpc_js_1 = require("@grpc/grpc-js");
|
|
34
|
+
const authMethods_1 = require("../utils/authMethods");
|
|
33
35
|
const generateAccessKeyId_1 = require("../utils/generateAccessKeyId");
|
|
36
|
+
const userPasswords_1 = require("../utils/userPasswords");
|
|
34
37
|
const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filename });
|
|
35
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Self-service sign-up with a password. Allowed only when sign-up is open and
|
|
40
|
+
* password sign-in is enabled; invited users are created by the invite.
|
|
41
|
+
*/
|
|
42
|
+
function createCreateUser(prisma, identityConfig) {
|
|
36
43
|
const createUser = (call, callback) => __awaiter(this, void 0, void 0, function* () {
|
|
37
44
|
const { request } = call;
|
|
38
45
|
const { name, email, password, avatar, phone } = request;
|
|
39
46
|
logger.verbose("call to createUser", { email });
|
|
47
|
+
if (!(0, authMethods_1.isOpenSignup)(identityConfig) ||
|
|
48
|
+
!(0, authMethods_1.isMethodEnabled)(identityConfig, "password")) {
|
|
49
|
+
return callback({
|
|
50
|
+
code: grpc_js_1.status.PERMISSION_DENIED,
|
|
51
|
+
message: "Sign-up is by invitation only"
|
|
52
|
+
});
|
|
53
|
+
}
|
|
40
54
|
const user = yield prisma.user.create({
|
|
41
|
-
data: {
|
|
42
|
-
|
|
43
|
-
email,
|
|
44
|
-
accessKeyId: (0, generateAccessKeyId_1.generateAccessKeyId)(generateAccessKeyId_1.AccessKeyIdType.USER),
|
|
45
|
-
password,
|
|
46
|
-
avatar,
|
|
47
|
-
phoneNumber: phone || undefined
|
|
48
|
-
}
|
|
55
|
+
data: Object.assign(Object.assign({ name,
|
|
56
|
+
email, accessKeyId: (0, generateAccessKeyId_1.generateAccessKeyId)(generateAccessKeyId_1.AccessKeyIdType.USER) }, (yield (0, userPasswords_1.passwordColumns)(password))), { avatar, phoneNumber: phone || undefined })
|
|
49
57
|
});
|
|
50
58
|
const { ref } = user;
|
|
51
59
|
callback(null, { ref });
|
|
@@ -64,45 +64,49 @@ exports.createCreateUserWithOauth2Code = createCreateUserWithOauth2Code;
|
|
|
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 nanoid_1 = require("nanoid");
|
|
68
67
|
const exchangeTokens_1 = require("../exchanges/exchangeTokens");
|
|
69
|
-
const
|
|
70
|
-
const
|
|
68
|
+
const authMethods_1 = require("../utils/authMethods");
|
|
69
|
+
const createGetUserByGitHubEmails_1 = require("../utils/createGetUserByGitHubEmails");
|
|
71
70
|
const getGitHubUserWithOauth2Code_1 = require("../utils/getGitHubUserWithOauth2Code");
|
|
71
|
+
const createUserFromGitHub_1 = require("./createUserFromGitHub");
|
|
72
72
|
const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filename });
|
|
73
73
|
function createCreateUserWithOauth2Code(prisma, identityConfig) {
|
|
74
74
|
const createUserWithOauth2Code = (call, callback) => __awaiter(this, void 0, void 0, function* () {
|
|
75
|
+
var _a;
|
|
75
76
|
const { request } = call;
|
|
76
77
|
const { code } = request;
|
|
77
78
|
logger.verbose("call to createCreateUserWithOauth2Code");
|
|
79
|
+
if (!(0, authMethods_1.isMethodEnabled)(identityConfig, "github") ||
|
|
80
|
+
!identityConfig.githubOauth2Config) {
|
|
81
|
+
return callback((0, authMethods_1.methodDisabledError)("github"));
|
|
82
|
+
}
|
|
83
|
+
if (!(0, authMethods_1.isOpenSignup)(identityConfig)) {
|
|
84
|
+
return callback({
|
|
85
|
+
code: grpc.status.PERMISSION_DENIED,
|
|
86
|
+
message: "Sign-up is by invitation only"
|
|
87
|
+
});
|
|
88
|
+
}
|
|
78
89
|
const userData = yield (0, getGitHubUserWithOauth2Code_1.getGitHubUserWithOauth2Code)({
|
|
79
90
|
clientId: identityConfig.githubOauth2Config.clientId,
|
|
80
91
|
clientSecret: identityConfig.githubOauth2Config.clientSecret,
|
|
92
|
+
url: identityConfig.githubOauth2Config.url,
|
|
93
|
+
apiUrl: identityConfig.githubOauth2Config.apiUrl,
|
|
81
94
|
code
|
|
82
95
|
});
|
|
83
96
|
if (!userData.email) {
|
|
84
97
|
return callback({
|
|
85
98
|
code: grpc.status.PERMISSION_DENIED,
|
|
86
|
-
message: "
|
|
99
|
+
message: "Your GitHub account has no verified primary email"
|
|
87
100
|
});
|
|
88
101
|
}
|
|
89
|
-
const userFromDB = yield (0,
|
|
102
|
+
const userFromDB = yield (0, createGetUserByGitHubEmails_1.createGetUserByGitHubEmails)(prisma)((_a = userData.verifiedEmails) !== null && _a !== void 0 ? _a : [userData.email]);
|
|
90
103
|
if (userFromDB) {
|
|
91
104
|
return callback({
|
|
92
105
|
code: grpc.status.ALREADY_EXISTS,
|
|
93
106
|
message: "User already exists"
|
|
94
107
|
});
|
|
95
108
|
}
|
|
96
|
-
const user = yield prisma.
|
|
97
|
-
data: {
|
|
98
|
-
name: userData.name,
|
|
99
|
-
email: userData.email,
|
|
100
|
-
accessKeyId: (0, generateAccessKeyId_1.generateAccessKeyId)(generateAccessKeyId_1.AccessKeyIdType.USER),
|
|
101
|
-
emailVerified: true,
|
|
102
|
-
password: (0, nanoid_1.customAlphabet)("1234567890abcdef", 10)(),
|
|
103
|
-
avatar: userData.avatar_url
|
|
104
|
-
}
|
|
105
|
-
});
|
|
109
|
+
const user = yield (0, createUserFromGitHub_1.createUserFromGitHub)(prisma)(Object.assign(Object.assign({}, userData), { email: userData.email }));
|
|
106
110
|
callback(null, yield (0, exchangeTokens_1.exchangeTokens)(prisma, identityConfig)(user.accessKeyId));
|
|
107
111
|
});
|
|
108
112
|
return (0, common_1.withErrorHandlingAndValidation)(createUserWithOauth2Code, common_1.Validators.createUserWithOauth2CodeRequestSchema);
|
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { GrpcErrorMessage } from "@fonoster/common";
|
|
20
20
|
import { Prisma } from "../db";
|
|
21
|
+
/**
|
|
22
|
+
* Sets a new password with an emailed code and signs the user out everywhere
|
|
23
|
+
* (their refresh tokens are revoked). Attempts are rate limited per email and
|
|
24
|
+
* per client IP, in this process's memory.
|
|
25
|
+
*/
|
|
21
26
|
declare function createResetPassword(prisma: Prisma): (call: {
|
|
22
27
|
request: unknown;
|
|
23
28
|
}, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
|
|
@@ -32,18 +32,43 @@ const common_1 = require("@fonoster/common");
|
|
|
32
32
|
const logger_1 = require("@fonoster/logger");
|
|
33
33
|
const types_1 = require("@fonoster/types");
|
|
34
34
|
const grpc_js_1 = require("@grpc/grpc-js");
|
|
35
|
+
const constants_1 = require("../constants");
|
|
36
|
+
const refreshTokens_1 = require("../exchanges/refreshTokens");
|
|
37
|
+
const clientAddress_1 = require("../utils/clientAddress");
|
|
35
38
|
const createIsValidVerificationCode_1 = require("../utils/createIsValidVerificationCode");
|
|
39
|
+
const rateLimiter_1 = require("../utils/rateLimiter");
|
|
40
|
+
const userPasswords_1 = require("../utils/userPasswords");
|
|
36
41
|
const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filename });
|
|
42
|
+
/**
|
|
43
|
+
* Sets a new password with an emailed code and signs the user out everywhere
|
|
44
|
+
* (their refresh tokens are revoked). Attempts are rate limited per email and
|
|
45
|
+
* per client IP, in this process's memory.
|
|
46
|
+
*/
|
|
37
47
|
function createResetPassword(prisma) {
|
|
38
48
|
const isValidVerificationCode = (0, createIsValidVerificationCode_1.createIsValidVerificationCode)(prisma);
|
|
49
|
+
const refreshTokens = (0, refreshTokens_1.createRefreshTokenStore)(prisma);
|
|
50
|
+
const { perEmail, perIp, windowMs } = constants_1.RESET_PASSWORD_ATTEMPT_LIMITS;
|
|
51
|
+
const withinEmailLimit = (0, rateLimiter_1.createSharedRateLimiter)(prisma, {
|
|
52
|
+
name: "reset-password:contact",
|
|
53
|
+
limit: perEmail,
|
|
54
|
+
windowMs
|
|
55
|
+
});
|
|
56
|
+
const withinIpLimit = (0, rateLimiter_1.createSharedRateLimiter)(prisma, {
|
|
57
|
+
name: "reset-password:ip",
|
|
58
|
+
limit: perIp,
|
|
59
|
+
windowMs
|
|
60
|
+
});
|
|
39
61
|
const resetPassword = (call, callback) => __awaiter(this, void 0, void 0, function* () {
|
|
40
62
|
const { request } = call;
|
|
41
63
|
const { username, password, verificationCode } = request;
|
|
42
|
-
logger.verbose("call to resetPassword", {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
64
|
+
logger.verbose("call to resetPassword", { username });
|
|
65
|
+
if (!(yield withinIpLimit((0, clientAddress_1.clientAddress)(call))) ||
|
|
66
|
+
!(yield withinEmailLimit(username.toLowerCase()))) {
|
|
67
|
+
return callback({
|
|
68
|
+
code: grpc_js_1.status.RESOURCE_EXHAUSTED,
|
|
69
|
+
message: "Too many password reset attempts; try again later"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
47
72
|
const isValid = yield isValidVerificationCode({
|
|
48
73
|
type: types_1.ContactType.EMAIL,
|
|
49
74
|
value: username,
|
|
@@ -55,10 +80,11 @@ function createResetPassword(prisma) {
|
|
|
55
80
|
message: "Invalid verification code"
|
|
56
81
|
});
|
|
57
82
|
}
|
|
58
|
-
yield prisma.user.update({
|
|
83
|
+
const user = yield prisma.user.update({
|
|
59
84
|
where: { email: username },
|
|
60
|
-
data: { password }
|
|
85
|
+
data: Object.assign(Object.assign({}, (yield (0, userPasswords_1.passwordColumns)(password))), { mustChangePassword: false })
|
|
61
86
|
});
|
|
87
|
+
yield refreshTokens.revokeAll(user.accessKeyId);
|
|
62
88
|
callback(null);
|
|
63
89
|
});
|
|
64
90
|
return (0, common_1.withErrorHandlingAndValidation)(resetPassword, common_1.Validators.resetPasswordRequestSchema);
|
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
import { GrpcErrorMessage } from "@fonoster/common";
|
|
20
20
|
import { Prisma } from "../db";
|
|
21
21
|
import { IdentityConfig } from "../exchanges/types";
|
|
22
|
+
/**
|
|
23
|
+
* Emails a reset link. The link always points at Identity's configured reset
|
|
24
|
+
* page (`resetPasswordUrl`); a URL in the request is ignored. Sends are rate
|
|
25
|
+
* limited per email and per client IP, in this process's memory (each replica
|
|
26
|
+
* counts on its own).
|
|
27
|
+
*/
|
|
22
28
|
declare function createSendResetPasswordCode(prisma: Prisma, identityConfig: IdentityConfig): (call: {
|
|
23
29
|
request: unknown;
|
|
24
30
|
}, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
|
|
@@ -31,16 +31,48 @@ exports.createSendResetPasswordCode = createSendResetPasswordCode;
|
|
|
31
31
|
const common_1 = require("@fonoster/common");
|
|
32
32
|
const logger_1 = require("@fonoster/logger");
|
|
33
33
|
const types_1 = require("@fonoster/types");
|
|
34
|
+
const grpc_js_1 = require("@grpc/grpc-js");
|
|
35
|
+
const constants_1 = require("../constants");
|
|
34
36
|
const utils_1 = require("../utils");
|
|
37
|
+
const clientAddress_1 = require("../utils/clientAddress");
|
|
35
38
|
const createGenerateVerificationCode_1 = require("../utils/createGenerateVerificationCode");
|
|
39
|
+
const rateLimiter_1 = require("../utils/rateLimiter");
|
|
36
40
|
const sendResetPasswordEmail_1 = require("./sendResetPasswordEmail");
|
|
37
41
|
const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filename });
|
|
42
|
+
const tooManyRequests = {
|
|
43
|
+
code: grpc_js_1.status.RESOURCE_EXHAUSTED,
|
|
44
|
+
message: "Too many password reset requests; try again later"
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Emails a reset link. The link always points at Identity's configured reset
|
|
48
|
+
* page (`resetPasswordUrl`); a URL in the request is ignored. Sends are rate
|
|
49
|
+
* limited per email and per client IP, in this process's memory (each replica
|
|
50
|
+
* counts on its own).
|
|
51
|
+
*/
|
|
38
52
|
function createSendResetPasswordCode(prisma, identityConfig) {
|
|
53
|
+
var _a;
|
|
39
54
|
const generateVerificationCode = (0, createGenerateVerificationCode_1.createGenerateVerificationCode)(prisma);
|
|
55
|
+
const { perEmail, perIp, windowMs } = constants_1.VERIFICATION_CODE_SEND_LIMITS;
|
|
56
|
+
const withinEmailLimit = (0, rateLimiter_1.createSharedRateLimiter)(prisma, {
|
|
57
|
+
name: "reset-code:contact",
|
|
58
|
+
limit: perEmail,
|
|
59
|
+
windowMs
|
|
60
|
+
});
|
|
61
|
+
const withinIpLimit = (0, rateLimiter_1.createSharedRateLimiter)(prisma, {
|
|
62
|
+
name: "reset-code:ip",
|
|
63
|
+
limit: perIp,
|
|
64
|
+
windowMs
|
|
65
|
+
});
|
|
66
|
+
const resetPasswordUrl = (_a = identityConfig.resetPasswordUrl) !== null && _a !== void 0 ? _a : `${identityConfig.issuer.replace(/\/+$/, "")}/reset-password`;
|
|
40
67
|
const sendResetPasswordCode = (call, callback) => __awaiter(this, void 0, void 0, function* () {
|
|
41
68
|
const { request } = call;
|
|
42
69
|
const { username } = request;
|
|
43
70
|
logger.verbose("call to sendResetPasswordCode", { username });
|
|
71
|
+
// Counted before the lookup, so the answer is the same for unknown emails.
|
|
72
|
+
if (!(yield withinIpLimit((0, clientAddress_1.clientAddress)(call))) ||
|
|
73
|
+
!(yield withinEmailLimit(username.toLowerCase()))) {
|
|
74
|
+
return callback(tooManyRequests);
|
|
75
|
+
}
|
|
44
76
|
const user = yield prisma.user.findUnique({
|
|
45
77
|
where: { email: username }
|
|
46
78
|
});
|
|
@@ -57,7 +89,7 @@ function createSendResetPasswordCode(prisma, identityConfig) {
|
|
|
57
89
|
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
58
90
|
yield (0, sendResetPasswordEmail_1.sendResetPasswordEmail)((0, utils_1.createSendEmail)(identityConfig), {
|
|
59
91
|
recipient: username,
|
|
60
|
-
resetPasswordUrl: `${
|
|
92
|
+
resetPasswordUrl: `${resetPasswordUrl}?token=${encodeURIComponent(encodedPayload)}`
|
|
61
93
|
});
|
|
62
94
|
callback(null);
|
|
63
95
|
});
|
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { GrpcErrorMessage } from "@fonoster/common";
|
|
20
20
|
import { Prisma } from "../db";
|
|
21
|
+
/**
|
|
22
|
+
* Updates the caller's own profile. A new password needs the current one
|
|
23
|
+
* (an access token alone can't change it) and signs the user out everywhere:
|
|
24
|
+
* every refresh token is revoked. Accounts without a password (GitHub or
|
|
25
|
+
* email-code sign-in) set one with the password-reset flow, which proves the
|
|
26
|
+
* email.
|
|
27
|
+
*/
|
|
21
28
|
declare function createUpdateUser(prisma: Prisma): (call: {
|
|
22
29
|
request: unknown;
|
|
23
30
|
}, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: unknown) => void) => Promise<void>;
|