@fonoster/identity 0.27.0 → 0.31.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.
Files changed (127) hide show
  1. package/README.md +25 -0
  2. package/dist/allowList.d.ts +4 -0
  3. package/dist/allowList.js +7 -1
  4. package/dist/apikeys/createCreateApiKey.js +16 -0
  5. package/dist/apikeys/createDeleteApiKey.js +22 -2
  6. package/dist/apikeys/createListApiKeys.js +14 -0
  7. package/dist/apikeys/createRegenerateApiKey.js +23 -2
  8. package/dist/config/index.d.ts +20 -0
  9. package/dist/config/index.js +36 -0
  10. package/dist/config/publishedKeys.d.ts +9 -0
  11. package/dist/config/publishedKeys.js +56 -0
  12. package/dist/config/schemas.d.ts +392 -0
  13. package/dist/config/schemas.js +163 -0
  14. package/dist/config/toIdentityConfig.d.ts +19 -0
  15. package/dist/config/toIdentityConfig.js +93 -0
  16. package/dist/constants.d.ts +28 -0
  17. package/dist/constants.js +29 -1
  18. package/dist/exchanges/createExchangeApiKey.js +4 -1
  19. package/dist/exchanges/createExchangeCredentials.d.ts +6 -1
  20. package/dist/exchanges/createExchangeCredentials.js +20 -2
  21. package/dist/exchanges/createExchangeOauth2Code.js +25 -2
  22. package/dist/exchanges/createExchangeRefreshToken.d.ts +9 -1
  23. package/dist/exchanges/createExchangeRefreshToken.js +57 -13
  24. package/dist/exchanges/createExchangeVerificationCode.d.ts +35 -0
  25. package/dist/exchanges/createExchangeVerificationCode.js +95 -0
  26. package/dist/exchanges/createRevokeToken.d.ts +33 -0
  27. package/dist/exchanges/createRevokeToken.js +64 -0
  28. package/dist/exchanges/exchangeTokens.d.ts +3 -1
  29. package/dist/exchanges/exchangeTokens.js +23 -5
  30. package/dist/exchanges/index.d.ts +2 -0
  31. package/dist/exchanges/index.js +2 -0
  32. package/dist/exchanges/payloads/users/createGetIdTokenPayload.js +3 -2
  33. package/dist/exchanges/refreshTokens.d.ts +30 -0
  34. package/dist/exchanges/refreshTokens.js +101 -0
  35. package/dist/exchanges/types.d.ts +53 -2
  36. package/dist/generated/@prisma/client/edge.js +27 -4
  37. package/dist/generated/@prisma/client/index-browser.js +24 -1
  38. package/dist/generated/@prisma/client/index.d.ts +3233 -417
  39. package/dist/generated/@prisma/client/index.js +31 -8
  40. package/dist/generated/@prisma/client/package.json +1 -1
  41. package/dist/generated/@prisma/client/runtime/edge-esm.js +2 -2
  42. package/dist/generated/@prisma/client/runtime/edge.js +2 -2
  43. package/dist/generated/@prisma/client/runtime/library.js +2 -2
  44. package/dist/generated/@prisma/client/runtime/react-native.js +2 -2
  45. package/dist/generated/@prisma/client/runtime/wasm-compiler-edge.js +2 -2
  46. package/dist/generated/@prisma/client/runtime/wasm-engine-edge.js +2 -2
  47. package/dist/generated/@prisma/client/schema.prisma +47 -7
  48. package/dist/generated/@prisma/client/wasm.js +27 -4
  49. package/dist/index.d.ts +1 -0
  50. package/dist/index.js +1 -0
  51. package/dist/server/config.d.ts +286 -55
  52. package/dist/server/config.js +73 -108
  53. package/dist/server/index.js +5 -3
  54. package/dist/service.d.ts +6 -0
  55. package/dist/service.js +11 -4
  56. package/dist/users/createCreateUser.d.ts +6 -1
  57. package/dist/users/createCreateUser.js +17 -9
  58. package/dist/users/createCreateUserWithOauth2Code.js +19 -15
  59. package/dist/users/createResetPassword.d.ts +5 -0
  60. package/dist/users/createResetPassword.js +33 -7
  61. package/dist/users/createSendResetPasswordCode.d.ts +6 -0
  62. package/dist/users/createSendResetPasswordCode.js +33 -1
  63. package/dist/users/createUpdateUser.d.ts +7 -0
  64. package/dist/users/createUpdateUser.js +41 -8
  65. package/dist/users/createUserFromGitHub.d.ts +40 -0
  66. package/dist/users/createUserFromGitHub.js +27 -0
  67. package/dist/users/index.d.ts +1 -0
  68. package/dist/users/index.js +1 -0
  69. package/dist/users/migratePasswordStorage.d.ts +7 -0
  70. package/dist/users/migratePasswordStorage.js +47 -0
  71. package/dist/users/types.d.ts +3 -1
  72. package/dist/users/upsertDefaultUser.js +6 -14
  73. package/dist/utils/authMethods.d.ts +31 -0
  74. package/dist/utils/authMethods.js +44 -0
  75. package/dist/utils/claimUnverifiedAccount.d.ts +35 -0
  76. package/dist/utils/claimUnverifiedAccount.js +43 -0
  77. package/dist/utils/clientAddress.d.ts +26 -0
  78. package/dist/utils/clientAddress.js +9 -0
  79. package/dist/utils/createGenerateVerificationCode.d.ts +4 -0
  80. package/dist/utils/createGenerateVerificationCode.js +12 -2
  81. package/dist/utils/createGenerateWorkspaceInviteToken.js +3 -0
  82. package/dist/utils/createGetApiKeyByAccessKeyId.d.ts +5 -1
  83. package/dist/utils/createGetApiKeyByAccessKeyId.js +5 -0
  84. package/dist/utils/createGetUserByAccessKeyId.d.ts +3 -1
  85. package/dist/utils/createGetUserByEmail.d.ts +3 -1
  86. package/dist/utils/createGetUserByGitHubEmails.d.ts +41 -0
  87. package/dist/utils/createGetUserByGitHubEmails.js +31 -0
  88. package/dist/utils/createGetUserByWorkspaceAccessKeyId.d.ts +3 -1
  89. package/dist/utils/createIsValidVerificationCode.d.ts +5 -0
  90. package/dist/utils/createIsValidVerificationCode.js +34 -7
  91. package/dist/utils/createSendEmail.d.ts +2 -1
  92. package/dist/utils/createSendEmail.js +17 -0
  93. package/dist/utils/createUpdateMembershipStatus.js +10 -3
  94. package/dist/utils/generateOneTimePassword.d.ts +3 -0
  95. package/dist/utils/generateOneTimePassword.js +25 -0
  96. package/dist/utils/getGitHubUserWithOauth2Code.d.ts +20 -2
  97. package/dist/utils/getGitHubUserWithOauth2Code.js +41 -9
  98. package/dist/utils/hasAccessToResource.js +4 -5
  99. package/dist/utils/isWorkspaceAdminCall.d.ts +8 -0
  100. package/dist/utils/isWorkspaceAdminCall.js +47 -0
  101. package/dist/utils/normalizeContact.d.ts +22 -0
  102. package/dist/utils/normalizeContact.js +28 -0
  103. package/dist/utils/passwords.d.ts +11 -0
  104. package/dist/utils/passwords.js +84 -0
  105. package/dist/utils/rateLimiter.d.ts +20 -0
  106. package/dist/utils/rateLimiter.js +108 -0
  107. package/dist/utils/signInThrottle.d.ts +47 -0
  108. package/dist/utils/signInThrottle.js +90 -0
  109. package/dist/utils/userPasswords.d.ts +25 -0
  110. package/dist/utils/userPasswords.js +98 -0
  111. package/dist/utils/withAccess.js +3 -2
  112. package/dist/verification/createSendVerificationCode.js +27 -1
  113. package/dist/verification/createVerifyCode.js +10 -0
  114. package/dist/workspaces/createCreateWorkspace.d.ts +7 -1
  115. package/dist/workspaces/createCreateWorkspace.js +41 -5
  116. package/dist/workspaces/createDeleteWorkspace.d.ts +2 -1
  117. package/dist/workspaces/createDeleteWorkspace.js +16 -2
  118. package/dist/workspaces/createInviteUserToWorkspace.js +19 -17
  119. package/dist/workspaces/createResendWorkspaceMembershipInvitation.js +17 -2
  120. package/dist/workspaces/createUpdateWorkspace.js +18 -4
  121. package/dist/workspaces/createWorkspaceDirectory.d.ts +29 -0
  122. package/dist/workspaces/createWorkspaceDirectory.js +42 -0
  123. package/dist/workspaces/index.d.ts +1 -0
  124. package/dist/workspaces/index.js +1 -0
  125. package/package.json +11 -8
  126. package/dist/generated/@prisma/client/libquery_engine-darwin-arm64.dylib.node +0 -0
  127. package/dist/generated/@prisma/client/runtime/wasm.js +0 -35
package/README.md CHANGED
@@ -22,6 +22,31 @@ This module offers comprehensive identity management functionality, including cr
22
22
  - **Authorization** via Role-Based Access Control (RBAC) with predefined and custom roles.
23
23
  - **Resource ownership** keyed by `accessKeyId` (`US…` for users, `WO…` for workspaces).
24
24
 
25
+ ## Running standalone
26
+
27
+ `fonoster-identity` (or the `fonoster/identity` image) serves Identity without the
28
+ telephony stack. It reads `identity.json` (or `.jsonc`/`.yaml`), found via
29
+ `--config <path>`, `/opt/fonoster/identity.json` or `./config/identity.json`, and
30
+ reads no environment variables. Fields sit at the root of the file:
31
+
32
+ ```json
33
+ {
34
+ "database": { "url": "postgresql://postgres:postgres@postgres:5432/fnidentity" },
35
+ "encryptionKey": "change-me",
36
+ "issuer": "https://id.acme.com",
37
+ "keys": {
38
+ "privateKeyPath": "/opt/fonoster/keys/private.pem",
39
+ "publicKeyPath": "/opt/fonoster/keys/public.pem"
40
+ },
41
+ "smtp": { "host": "smtp.acme.com", "port": 587, "sender": "no-reply@acme.com", "auth": { "user": "ops", "pass": "secret" } },
42
+ "auth": { "methods": ["password"], "signup": "invite" },
43
+ "defaultUser": { "name": "Admin", "email": "admin@acme.com", "password": "changeme" }
44
+ }
45
+ ```
46
+
47
+ `npm run db:deploy -- --config <path>` applies the migrations using the same file.
48
+ Every key is described in [docs/configuration.md](../../docs/configuration.md).
49
+
25
50
  ## Specification
26
51
 
27
52
  The normative specification for this module — entities and resource ownership, the RBAC model, the
@@ -20,6 +20,10 @@
20
20
  * gRPC methods reachable without an access token. This is the single source of
21
21
  * truth for Identity's public methods, shared by the standalone Identity service
22
22
  * and the apiserver monolith (which appends its own non-identity entries).
23
+ *
24
+ * CreateWorkspace is deliberately absent: its handler takes the owner from the
25
+ * caller's access token, so a tokenless call must stop at the interceptor
26
+ * (UNAUTHENTICATED) rather than reach it.
23
27
  */
24
28
  declare const identityAllowList: string[];
25
29
  export { identityAllowList };
package/dist/allowList.js CHANGED
@@ -23,16 +23,22 @@ exports.identityAllowList = void 0;
23
23
  * gRPC methods reachable without an access token. This is the single source of
24
24
  * truth for Identity's public methods, shared by the standalone Identity service
25
25
  * and the apiserver monolith (which appends its own non-identity entries).
26
+ *
27
+ * CreateWorkspace is deliberately absent: its handler takes the owner from the
28
+ * caller's access token, so a tokenless call must stop at the interceptor
29
+ * (UNAUTHENTICATED) rather than reach it.
26
30
  */
27
31
  const identityAllowList = [
28
32
  "/grpc.health.v1.Health/Check",
29
33
  "/fonoster.identity.v1beta2.Identity/CreateUser",
30
34
  "/fonoster.identity.v1beta2.Identity/CreateUserWithOauth2Code",
31
- "/fonoster.identity.v1beta2.Identity/CreateWorkspace",
32
35
  "/fonoster.identity.v1beta2.Identity/ExchangeApiKey",
33
36
  "/fonoster.identity.v1beta2.Identity/ExchangeCredentials",
34
37
  "/fonoster.identity.v1beta2.Identity/ExchangeOauth2Code",
35
38
  "/fonoster.identity.v1beta2.Identity/ExchangeRefreshToken",
39
+ "/fonoster.identity.v1beta2.Identity/ExchangeVerificationCode",
40
+ // Holding the refresh token is what authorizes revoking it (sign-out).
41
+ "/fonoster.identity.v1beta2.Identity/RevokeToken",
36
42
  "/fonoster.identity.v1beta2.Identity/SendVerificationCode",
37
43
  "/fonoster.identity.v1beta2.Identity/VerifyCode",
38
44
  "/fonoster.identity.v1beta2.Identity/GetPublicKey",
@@ -30,18 +30,34 @@ exports.createCreateApiKey = createCreateApiKey;
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");
33
34
  const generateAccessKeyId_1 = require("../utils/generateAccessKeyId");
34
35
  const generateAccessKeySecret_1 = require("../utils/generateAccessKeySecret");
36
+ const isWorkspaceAdminCall_1 = require("../utils/isWorkspaceAdminCall");
35
37
  const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filename });
38
+ const notAdmin = {
39
+ code: grpc_js_1.status.PERMISSION_DENIED,
40
+ message: "Only workspace owners and admins can manage API keys"
41
+ };
36
42
  function createCreateApiKey(prisma) {
37
43
  const createApiKey = (call, callback) => __awaiter(this, void 0, void 0, function* () {
38
44
  const accessKeyId = (0, common_1.getAccessKeyIdFromCall)(call);
39
45
  const { request } = call;
40
46
  const { role, expiresAt } = request;
41
47
  logger.info("creating new ApiKey", { accessKeyId, role, expiresAt });
48
+ // Keys act as WORKSPACE_ADMIN, so only owners and admins may mint them.
49
+ if (!(0, isWorkspaceAdminCall_1.isWorkspaceAdminCall)(call)) {
50
+ return callback(notAdmin);
51
+ }
42
52
  const workspace = yield prisma.workspace.findUnique({
43
53
  where: { accessKeyId }
44
54
  });
55
+ if (!workspace) {
56
+ return callback({
57
+ code: grpc_js_1.status.NOT_FOUND,
58
+ message: "Workspace not found"
59
+ });
60
+ }
45
61
  const { ref } = workspace;
46
62
  const response = yield prisma.apiKey.create({
47
63
  data: {
@@ -30,17 +30,37 @@ exports.createDeleteApiKey = createDeleteApiKey;
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 refreshTokens_1 = require("../exchanges/refreshTokens");
35
+ const isWorkspaceAdminCall_1 = require("../utils/isWorkspaceAdminCall");
33
36
  const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filename });
37
+ const notAdmin = {
38
+ code: grpc_js_1.status.PERMISSION_DENIED,
39
+ message: "Only workspace owners and admins can manage API keys"
40
+ };
34
41
  function createDeleteApiKey(prisma) {
42
+ const refreshTokens = (0, refreshTokens_1.createRefreshTokenStore)(prisma);
35
43
  const deleteApiKey = (call, callback) => __awaiter(this, void 0, void 0, function* () {
36
44
  const { request } = call;
37
45
  const { ref } = request;
38
- logger.info("deleting ApiKey", { ref });
46
+ const accessKeyId = (0, common_1.getAccessKeyIdFromCall)(call);
47
+ logger.info("deleting ApiKey", { ref, accessKeyId });
48
+ if (!(0, isWorkspaceAdminCall_1.isWorkspaceAdminCall)(call)) {
49
+ return callback(notAdmin);
50
+ }
51
+ // Only a key of the caller's workspace; others look like they don't exist.
52
+ const key = yield prisma.apiKey.findFirst({
53
+ where: { ref, workspace: { accessKeyId } }
54
+ });
55
+ if (!key) {
56
+ return callback({ code: grpc_js_1.status.NOT_FOUND, message: "ApiKey not found" });
57
+ }
39
58
  yield prisma.apiKey.delete({
40
59
  where: {
41
- ref
60
+ ref: key.ref
42
61
  }
43
62
  });
63
+ yield refreshTokens.revokeAll(key.accessKeyId);
44
64
  callback(null, { ref });
45
65
  });
46
66
  return (0, common_1.withErrorHandlingAndValidation)(deleteApiKey, common_1.Validators.emptySchema);
@@ -30,6 +30,8 @@ exports.createListApiKeys = createListApiKeys;
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 isWorkspaceAdminCall_1 = require("../utils/isWorkspaceAdminCall");
33
35
  const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filename });
34
36
  function createListApiKeys(prisma) {
35
37
  const listApiKeys = (call, callback) => __awaiter(this, void 0, void 0, function* () {
@@ -37,11 +39,23 @@ function createListApiKeys(prisma) {
37
39
  const { pageSize, pageToken } = call.request;
38
40
  const accessKeyId = (0, common_1.getAccessKeyIdFromCall)(call);
39
41
  logger.verbose("list keys for workspace", { accessKeyId });
42
+ if (!(0, isWorkspaceAdminCall_1.isWorkspaceAdminCall)(call)) {
43
+ return callback({
44
+ code: grpc_js_1.status.PERMISSION_DENIED,
45
+ message: "Only workspace owners and admins can manage API keys"
46
+ });
47
+ }
40
48
  const workspace = yield prisma.workspace.findUnique({
41
49
  where: {
42
50
  accessKeyId
43
51
  }
44
52
  });
53
+ if (!workspace) {
54
+ return callback({
55
+ code: grpc_js_1.status.NOT_FOUND,
56
+ message: "Workspace not found"
57
+ });
58
+ }
45
59
  const keys = yield prisma.apiKey.findMany({
46
60
  where: {
47
61
  workspaceRef: workspace.ref
@@ -30,21 +30,42 @@ exports.createRegenerateApiKey = createRegenerateApiKey;
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 refreshTokens_1 = require("../exchanges/refreshTokens");
33
35
  const generateAccessKeySecret_1 = require("../utils/generateAccessKeySecret");
36
+ const isWorkspaceAdminCall_1 = require("../utils/isWorkspaceAdminCall");
34
37
  const logger = (0, logger_1.getLogger)({ service: "identity", filePath: __filename });
38
+ const notAdmin = {
39
+ code: grpc_js_1.status.PERMISSION_DENIED,
40
+ message: "Only workspace owners and admins can manage API keys"
41
+ };
35
42
  function createRegenerateApiKey(prisma) {
43
+ const refreshTokens = (0, refreshTokens_1.createRefreshTokenStore)(prisma);
36
44
  const regenerateApiKey = (call, callback) => __awaiter(this, void 0, void 0, function* () {
37
45
  const { request } = call;
38
46
  const { ref } = request;
39
- logger.info("regenerating ApiKey", { ref });
47
+ const accessKeyId = (0, common_1.getAccessKeyIdFromCall)(call);
48
+ logger.info("regenerating ApiKey", { ref, accessKeyId });
49
+ if (!(0, isWorkspaceAdminCall_1.isWorkspaceAdminCall)(call)) {
50
+ return callback(notAdmin);
51
+ }
52
+ // Only a key of the caller's workspace; others look like they don't exist.
53
+ const key = yield prisma.apiKey.findFirst({
54
+ where: { ref, workspace: { accessKeyId } }
55
+ });
56
+ if (!key) {
57
+ return callback({ code: grpc_js_1.status.NOT_FOUND, message: "ApiKey not found" });
58
+ }
40
59
  const response = yield prisma.apiKey.update({
41
60
  where: {
42
- ref
61
+ ref: key.ref
43
62
  },
44
63
  data: {
45
64
  accessKeySecret: (0, generateAccessKeySecret_1.generateAccessKeySecret)()
46
65
  }
47
66
  });
67
+ // The old secret's sessions end with it.
68
+ yield refreshTokens.revokeAll(key.accessKeyId);
48
69
  callback(null, {
49
70
  ref: response.ref,
50
71
  accessKeyId: response.accessKeyId,
@@ -0,0 +1,20 @@
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
+ export * from "./schemas";
20
+ export * from "./toIdentityConfig";
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ /**
18
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
19
+ * http://github.com/fonoster/fonoster
20
+ *
21
+ * This file is part of Fonoster
22
+ *
23
+ * Licensed under the MIT License (the "License");
24
+ * you may not use this file except in compliance with
25
+ * the License. You may obtain a copy of the License at
26
+ *
27
+ * https://opensource.org/licenses/MIT
28
+ *
29
+ * Unless required by applicable law or agreed to in writing, software
30
+ * distributed under the License is distributed on an "AS IS" BASIS,
31
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32
+ * See the License for the specific language governing permissions and
33
+ * limitations under the License.
34
+ */
35
+ __exportStar(require("./schemas"), exports);
36
+ __exportStar(require("./toIdentityConfig"), exports);
@@ -0,0 +1,9 @@
1
+ /** SHA-256 of a PEM key's public part (SPKI DER), or undefined if it doesn't parse. */
2
+ declare function publicKeyFingerprint(pem: string): string | undefined;
3
+ /** Throws when a signing key or the encryption key is one that was published. */
4
+ declare function assertUnpublishedKeys(keys: {
5
+ privateKey: string;
6
+ publicKey: string;
7
+ encryptionKey: string;
8
+ }): void;
9
+ export { assertUnpublishedKeys, publicKeyFingerprint };
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assertUnpublishedKeys = assertUnpublishedKeys;
4
+ exports.publicKeyFingerprint = publicKeyFingerprint;
5
+ /**
6
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
7
+ * http://github.com/fonoster/fonoster
8
+ *
9
+ * This file is part of Fonoster
10
+ *
11
+ * Licensed under the MIT License (the "License");
12
+ * you may not use this file except in compliance with
13
+ * the License. You may obtain a copy of the License at
14
+ *
15
+ * https://opensource.org/licenses/MIT
16
+ *
17
+ * Unless required by applicable law or agreed to in writing, software
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ * See the License for the specific language governing permissions and
21
+ * limitations under the License.
22
+ */
23
+ const crypto_1 = require("crypto");
24
+ /**
25
+ * Keys that were committed to the repository (config/keys and the root config examples) before
26
+ * they were removed. They are public, so anyone could sign tokens or decrypt credentials with
27
+ * them: Identity refuses to start with one.
28
+ */
29
+ const PUBLISHED_SIGNING_KEY_FINGERPRINTS = [
30
+ // config/keys/public.pem (SHA-256 of the SPKI DER)
31
+ "b8dcd305f9d55674fb796641f967ee35b1fd8c464432f842320208e717e9db97"
32
+ ];
33
+ const PUBLISHED_ENCRYPTION_KEYS = [
34
+ "k1.aesgcm256.MmPSvzCG9fk654bAbl30tsqq4h9d3N4F11hlue8bGAY=",
35
+ // the placeholder in config/fonoster.example.yaml
36
+ "k1.aesgcm256.GENERATE-YOUR-OWN-with-npx-47ng-cloak-gen00="
37
+ ];
38
+ /** SHA-256 of a PEM key's public part (SPKI DER), or undefined if it doesn't parse. */
39
+ function publicKeyFingerprint(pem) {
40
+ try {
41
+ const der = (0, crypto_1.createPublicKey)(pem).export({ type: "spki", format: "der" });
42
+ return (0, crypto_1.createHash)("sha256").update(der).digest("hex");
43
+ }
44
+ catch (_a) {
45
+ return undefined;
46
+ }
47
+ }
48
+ /** Throws when a signing key or the encryption key is one that was published. */
49
+ function assertUnpublishedKeys(keys) {
50
+ var _a;
51
+ const published = [keys.privateKey, keys.publicKey].some((pem) => { var _a; return PUBLISHED_SIGNING_KEY_FINGERPRINTS.includes((_a = publicKeyFingerprint(pem)) !== null && _a !== void 0 ? _a : ""); });
52
+ if (published)
53
+ throw new Error("identity signing key is the development key once committed to the Fonoster repository: generate your own (fonoster-config init, or openssl genrsa 2048)");
54
+ if (PUBLISHED_ENCRYPTION_KEYS.includes((_a = keys.encryptionKey) === null || _a === void 0 ? void 0 : _a.trim()))
55
+ throw new Error("encryption.key is the example key once committed to the Fonoster repository: generate your own (npx @47ng/cloak generate)");
56
+ }