tempest-express-sdk 0.6.0 → 0.7.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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { VERSION } from './chunk-3IDD2UXU.js';
1
+ export { VERSION } from './chunk-PKUUVK7K.js';
2
2
  import { AsyncLocalStorage } from 'async_hooks';
3
3
  import { extendZodWithOpenApi, OpenAPIRegistry, OpenApiGeneratorV31, OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi';
4
4
  export { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';
@@ -8512,6 +8512,17 @@ var authResponseSchema = z.object({
8512
8512
  user: userPublicSchema,
8513
8513
  tokens: tokenPairSchema
8514
8514
  }).openapi("AuthResponse");
8515
+ var mfaEnrollResponseSchema = z.object({
8516
+ secret: z.string().openapi({ description: "Base32 TOTP secret (manual entry)." }),
8517
+ otpauthUri: z.string().openapi({ description: "otpauth:// URI to render as QR." })
8518
+ }).openapi("MfaEnrollResponse");
8519
+ var mfaCodeSchema = z.object({ code: z.string().min(1).openapi({ description: "Authenticator code." }) }).openapi("MfaCode");
8520
+ var activationSchema = z.object({ token: z.string().min(1).openapi({ description: "Activation token." }) }).openapi("Activation");
8521
+ var passwordResetRequestSchema = z.object({ email: z.string().email().openapi({ description: "Account email." }) }).openapi("PasswordResetRequest");
8522
+ var passwordResetConfirmSchema = z.object({
8523
+ token: z.string().min(1).openapi({ description: "Reset token." }),
8524
+ password: z.string().min(1).openapi({ description: "New plaintext password." })
8525
+ }).openapi("PasswordResetConfirm");
8515
8526
 
8516
8527
  // src/auth/service.ts
8517
8528
  function toPublic(user) {
@@ -8624,6 +8635,178 @@ var UserAuthService = class {
8624
8635
  }
8625
8636
  };
8626
8637
 
8638
+ // src/auth/mfa.ts
8639
+ var MfaService = class {
8640
+ store;
8641
+ totp;
8642
+ /**
8643
+ * @param options - Store and TOTP helper.
8644
+ */
8645
+ constructor(options) {
8646
+ this.store = options.store;
8647
+ this.totp = options.totp;
8648
+ }
8649
+ /**
8650
+ * Begin enrollment: generate and persist a secret, return the QR URI.
8651
+ *
8652
+ * @param userId - The enrolling user.
8653
+ * @param accountName - Label shown in the authenticator (usually the email).
8654
+ * @returns The secret and provisioning URI.
8655
+ */
8656
+ async enroll(userId, accountName) {
8657
+ const secret = this.totp.generateSecret();
8658
+ await this.store.setSecret(userId, secret);
8659
+ await this.store.setEnabled(userId, false);
8660
+ return { secret, otpauthUri: this.totp.provisioningUri(secret, accountName) };
8661
+ }
8662
+ /**
8663
+ * Confirm enrollment by verifying a code, enabling MFA on success.
8664
+ *
8665
+ * @param userId - The user.
8666
+ * @param code - The 6-digit code from the authenticator.
8667
+ * @throws {ValidationException} When no secret is pending or the code is wrong.
8668
+ */
8669
+ async confirm(userId, code) {
8670
+ const secret = await this.store.getSecret(userId);
8671
+ if (!secret) throw new ValidationException({ message: "MFA not initialized" });
8672
+ if (!this.totp.verify(secret, code)) {
8673
+ throw new ValidationException({ message: "Invalid MFA code" });
8674
+ }
8675
+ await this.store.setEnabled(userId, true);
8676
+ }
8677
+ /**
8678
+ * Verify a code (login step). Returns `false` without throwing.
8679
+ *
8680
+ * @param userId - The user.
8681
+ * @param code - The submitted code.
8682
+ * @returns `true` when the code is valid.
8683
+ */
8684
+ async verify(userId, code) {
8685
+ const secret = await this.store.getSecret(userId);
8686
+ return secret ? this.totp.verify(secret, code) : false;
8687
+ }
8688
+ /**
8689
+ * Disable MFA after verifying a code.
8690
+ *
8691
+ * @param userId - The user.
8692
+ * @param code - The submitted code.
8693
+ * @throws {ValidationException} When the code is invalid.
8694
+ */
8695
+ async disable(userId, code) {
8696
+ if (!await this.verify(userId, code)) {
8697
+ throw new ValidationException({ message: "Invalid MFA code" });
8698
+ }
8699
+ await this.store.setEnabled(userId, false);
8700
+ await this.store.setSecret(userId, "");
8701
+ }
8702
+ };
8703
+
8704
+ // src/auth/activation.ts
8705
+ var ActivationService = class {
8706
+ store;
8707
+ ttlSeconds;
8708
+ /**
8709
+ * @param options - Store and token TTL.
8710
+ */
8711
+ constructor(options) {
8712
+ this.store = options.store;
8713
+ this.ttlSeconds = options.ttlSeconds ?? 60 * 60 * 24;
8714
+ }
8715
+ /**
8716
+ * Start activation: issue a token and persist its hash.
8717
+ *
8718
+ * @param userId - The user to activate.
8719
+ * @returns The one-time plaintext token (embed in the activation link).
8720
+ */
8721
+ async start(userId) {
8722
+ const { plaintext, tokenHash } = generateOpaqueToken();
8723
+ await this.store.saveActivationToken(
8724
+ userId,
8725
+ tokenHash,
8726
+ Date.now() + this.ttlSeconds * 1e3
8727
+ );
8728
+ return plaintext;
8729
+ }
8730
+ /**
8731
+ * Activate an account from a token.
8732
+ *
8733
+ * @param token - The plaintext token from the activation link.
8734
+ * @returns The activated user id.
8735
+ * @throws {InvalidTokenException} When the token is unknown or expired.
8736
+ */
8737
+ async activate(token) {
8738
+ const tokenHash = hashOpaqueToken(token);
8739
+ const record = await this.store.findActivationToken(tokenHash);
8740
+ if (!record || record.expiresAt <= Date.now()) {
8741
+ throw new InvalidTokenException({ message: "Invalid or expired activation token" });
8742
+ }
8743
+ await this.store.activate(record.userId);
8744
+ await this.store.clearActivationToken(tokenHash);
8745
+ return record.userId;
8746
+ }
8747
+ };
8748
+
8749
+ // src/auth/passwordReset.ts
8750
+ var PasswordResetService = class {
8751
+ store;
8752
+ password;
8753
+ ttlSeconds;
8754
+ passwordMinLength;
8755
+ /**
8756
+ * @param options - Store, password hasher and policy.
8757
+ */
8758
+ constructor(options) {
8759
+ this.store = options.store;
8760
+ this.password = options.password;
8761
+ this.ttlSeconds = options.ttlSeconds ?? 3600;
8762
+ this.passwordMinLength = options.passwordMinLength ?? 12;
8763
+ }
8764
+ /**
8765
+ * Request a reset for `email`.
8766
+ *
8767
+ * Returns the plaintext token only when the email maps to a user; otherwise
8768
+ * `null`. Callers should respond with the same success shape either way to
8769
+ * avoid user enumeration — email the token only when present.
8770
+ *
8771
+ * @param email - The account email.
8772
+ * @returns The one-time token, or `null` when no user matches.
8773
+ */
8774
+ async request(email) {
8775
+ const userId = await this.store.findUserIdByEmail(email.toLowerCase());
8776
+ if (!userId) return null;
8777
+ const { plaintext, tokenHash } = generateOpaqueToken();
8778
+ await this.store.saveResetToken(
8779
+ userId,
8780
+ tokenHash,
8781
+ Date.now() + this.ttlSeconds * 1e3
8782
+ );
8783
+ return plaintext;
8784
+ }
8785
+ /**
8786
+ * Confirm a reset: validate the token + new password and rehash.
8787
+ *
8788
+ * @param token - The plaintext reset token.
8789
+ * @param newPassword - The new plaintext password.
8790
+ * @throws {ValidationException} When the new password is too short.
8791
+ * @throws {InvalidTokenException} When the token is unknown or expired.
8792
+ */
8793
+ async confirm(token, newPassword) {
8794
+ if (newPassword.length < this.passwordMinLength) {
8795
+ throw new ValidationException({
8796
+ message: `Password must be at least ${this.passwordMinLength} characters`,
8797
+ details: { minLength: this.passwordMinLength }
8798
+ });
8799
+ }
8800
+ const tokenHash = hashOpaqueToken(token);
8801
+ const record = await this.store.findResetToken(tokenHash);
8802
+ if (!record || record.expiresAt <= Date.now()) {
8803
+ throw new InvalidTokenException({ message: "Invalid or expired reset token" });
8804
+ }
8805
+ await this.store.updatePassword(record.userId, await this.password.hash(newPassword));
8806
+ await this.store.clearResetToken(tokenHash);
8807
+ }
8808
+ };
8809
+
8627
8810
  // src/auth/middleware.ts
8628
8811
  function bearerToken(req) {
8629
8812
  const header = req.header("authorization");
@@ -8726,6 +8909,53 @@ function makeAuthRouter(options) {
8726
8909
  }
8727
8910
  res.json(claims);
8728
8911
  });
8912
+ if (options.activation) {
8913
+ const activation = options.activation;
8914
+ router.post(`${prefix}/activate`, async (req, res) => {
8915
+ const { token } = activationSchema.parse(req.body);
8916
+ const userId = await activation.activate(token);
8917
+ res.json({ activated: true, userId });
8918
+ });
8919
+ }
8920
+ if (options.passwordReset) {
8921
+ const reset = options.passwordReset;
8922
+ router.post(`${prefix}/password-reset/request`, async (req, res) => {
8923
+ const { email } = passwordResetRequestSchema.parse(req.body);
8924
+ const token = await reset.request(email);
8925
+ res.status(202).json({ requested: true, ...token ? { token } : {} });
8926
+ });
8927
+ router.post(`${prefix}/password-reset/confirm`, async (req, res) => {
8928
+ const { token, password } = passwordResetConfirmSchema.parse(req.body);
8929
+ await reset.confirm(token, password);
8930
+ res.json({ reset: true });
8931
+ });
8932
+ }
8933
+ if (options.mfa) {
8934
+ const mfa = options.mfa;
8935
+ const requireUser = (req) => {
8936
+ const claims = getAuth(req);
8937
+ if (!claims || typeof claims.sub !== "string") {
8938
+ throw new UnauthorizedException({ message: "Not authenticated" });
8939
+ }
8940
+ return claims.sub;
8941
+ };
8942
+ router.post(`${prefix}/mfa/enroll`, makeJwtAuthMiddleware(jwt), async (req, res) => {
8943
+ const claims = getAuth(req);
8944
+ const userId = requireUser(req);
8945
+ const label = typeof claims?.email === "string" ? claims.email : userId;
8946
+ res.json(await mfa.enroll(userId, label));
8947
+ });
8948
+ router.post(`${prefix}/mfa/confirm`, makeJwtAuthMiddleware(jwt), async (req, res) => {
8949
+ const { code } = mfaCodeSchema.parse(req.body);
8950
+ await mfa.confirm(requireUser(req), code);
8951
+ res.json({ enabled: true });
8952
+ });
8953
+ router.post(`${prefix}/mfa/disable`, makeJwtAuthMiddleware(jwt), async (req, res) => {
8954
+ const { code } = mfaCodeSchema.parse(req.body);
8955
+ await mfa.disable(requireUser(req), code);
8956
+ res.json({ enabled: false });
8957
+ });
8958
+ }
8729
8959
  return router;
8730
8960
  }
8731
8961
  var logger2 = new JSONLogger("tempest_express_sdk.api.handlers");
@@ -8993,6 +9223,6 @@ function runServer(app, options = {}) {
8993
9223
  });
8994
9224
  }
8995
9225
 
8996
- export { AdminSite, AppException, AttemptThrottle, BaseController, BaseModel, BaseService, CEP_PATTERN, CNPJ_PATTERN, CPF_PATTERN, CircuitOpenError, CompositeFeatureFlagBackend, ConflictException, DEFAULT_LOCALE, EmailUtils, EnvFeatureFlagBackend, EventStream, ExpiredTokenException, FeatureFlags, ForbiddenException, HTTPClient, HTTP_500_MARKER, InvalidTokenException, JSONLogger, JWTUtils, LocalUploadStorage, MemoryBroker, MemoryCacheManager, MemoryFeatureFlagBackend, MemorySessionStore, MemoryThrottleBackend, MessageCatalog, MetricsUtils, NotFoundException, PHONE_BR_PATTERN, PasswordUtils, REQUEST_ID_HEADER, RabbitBroker, RedisCacheManager, Region, RetryPolicy, SSEBroker, ServerSentEvent, SessionService, TOTPHelper, TaskManager, TelegramProvider, TooManyRequestsException, TwilioSmsProvider, UF, UnauthorizedException, UserAuthService, ValidationException, WebPushDispatcher, WebPushError, WebPushGoneError, WebSocketHub, WhatsAppProvider, attachWebSocketHub, authResponseSchema, baseAppSettingsSchema, baseAppSettingsShape, baseResponseSchema, bearerToken, buildContentDisposition, cached3 as cached, cepField, citiesByUf, cnpjField, coerceFlag, configureLogging, corsSettingsShape, cpfField, cpfOrCnpjField, createApp, createOpenApiRegistry, createdByColumn, cursorPaginationFilterSchema, cursorPaginationSchema, databaseSettingsShape, decodeCursor, defaultMessageCatalog, defineEnum, deletedAtColumn, encodeCursor, generateOpaqueToken, generateOpenApiDocument, getAuth, getClientIp, getConditions, getPaginationConditions, getRequestId, getState, hashOpaqueToken, inboundMessageSchema, isValidCep, isValidCity, isValidCnpj, isValidCpf, isValidCpfCnpj, isValidPhoneBr, isValidUf, listStates, loadSettings, loginSchema, makeAdminRouter, makeAppExceptionHandler, makeAuthRouter, makeFlagGuard, makeHealthRouter, makeJwtAuthMiddleware, makeSessionMiddleware, makeTwilioWebhookRouter, makeUnhandledExceptionHandler, makeWhatsAppWebhookRouter, modifyDict, mountOpenApiJson, mountRedoc, mountSwaggerUi, normalizeCep, normalizeCnpj, normalizeCpf, normalizeCpfCnpj, normalizePhoneBr, normalizeUf, notFoundHandler, onlyDigits, paginationFilterSchema, paginationSchema, parseAcceptLanguage, parseCookies, phoneBrField, refreshSchema, registerExceptionHandlers, requestIdMiddleware, requireRoles, runServer, runWithRequestContext, serverSettingsShape, sessionCookie, setRequestId, signupSchema, sseResponse, statesByRegion, tableNameFor, toDict, toUtc, tokenFromUrl, tokenPairSchema, ufField, updatedByColumn, userPublicSchema, utcnow, validateTwilioSignature, verifyOpaqueToken, webPushKeysSchema, webPushPayloadSchema, webPushSubscriptionSchema, wsEnvelopeSchema };
9226
+ export { ActivationService, AdminSite, AppException, AttemptThrottle, BaseController, BaseModel, BaseService, CEP_PATTERN, CNPJ_PATTERN, CPF_PATTERN, CircuitOpenError, CompositeFeatureFlagBackend, ConflictException, DEFAULT_LOCALE, EmailUtils, EnvFeatureFlagBackend, EventStream, ExpiredTokenException, FeatureFlags, ForbiddenException, HTTPClient, HTTP_500_MARKER, InvalidTokenException, JSONLogger, JWTUtils, LocalUploadStorage, MemoryBroker, MemoryCacheManager, MemoryFeatureFlagBackend, MemorySessionStore, MemoryThrottleBackend, MessageCatalog, MetricsUtils, MfaService, NotFoundException, PHONE_BR_PATTERN, PasswordResetService, PasswordUtils, REQUEST_ID_HEADER, RabbitBroker, RedisCacheManager, Region, RetryPolicy, SSEBroker, ServerSentEvent, SessionService, TOTPHelper, TaskManager, TelegramProvider, TooManyRequestsException, TwilioSmsProvider, UF, UnauthorizedException, UserAuthService, ValidationException, WebPushDispatcher, WebPushError, WebPushGoneError, WebSocketHub, WhatsAppProvider, activationSchema, attachWebSocketHub, authResponseSchema, baseAppSettingsSchema, baseAppSettingsShape, baseResponseSchema, bearerToken, buildContentDisposition, cached3 as cached, cepField, citiesByUf, cnpjField, coerceFlag, configureLogging, corsSettingsShape, cpfField, cpfOrCnpjField, createApp, createOpenApiRegistry, createdByColumn, cursorPaginationFilterSchema, cursorPaginationSchema, databaseSettingsShape, decodeCursor, defaultMessageCatalog, defineEnum, deletedAtColumn, encodeCursor, generateOpaqueToken, generateOpenApiDocument, getAuth, getClientIp, getConditions, getPaginationConditions, getRequestId, getState, hashOpaqueToken, inboundMessageSchema, isValidCep, isValidCity, isValidCnpj, isValidCpf, isValidCpfCnpj, isValidPhoneBr, isValidUf, listStates, loadSettings, loginSchema, makeAdminRouter, makeAppExceptionHandler, makeAuthRouter, makeFlagGuard, makeHealthRouter, makeJwtAuthMiddleware, makeSessionMiddleware, makeTwilioWebhookRouter, makeUnhandledExceptionHandler, makeWhatsAppWebhookRouter, mfaCodeSchema, mfaEnrollResponseSchema, modifyDict, mountOpenApiJson, mountRedoc, mountSwaggerUi, normalizeCep, normalizeCnpj, normalizeCpf, normalizeCpfCnpj, normalizePhoneBr, normalizeUf, notFoundHandler, onlyDigits, paginationFilterSchema, paginationSchema, parseAcceptLanguage, parseCookies, passwordResetConfirmSchema, passwordResetRequestSchema, phoneBrField, refreshSchema, registerExceptionHandlers, requestIdMiddleware, requireRoles, runServer, runWithRequestContext, serverSettingsShape, sessionCookie, setRequestId, signupSchema, sseResponse, statesByRegion, tableNameFor, toDict, toUtc, tokenFromUrl, tokenPairSchema, ufField, updatedByColumn, userPublicSchema, utcnow, validateTwilioSignature, verifyOpaqueToken, webPushKeysSchema, webPushPayloadSchema, webPushSubscriptionSchema, wsEnvelopeSchema };
8997
9227
  //# sourceMappingURL=index.js.map
8998
9228
  //# sourceMappingURL=index.js.map