diff --git a/.changeset/config.json b/.changeset/config.json index c393b904e9..ead8c55b2c 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -44,7 +44,7 @@ "@objectstack/plugin-reports", "@objectstack/plugin-security", "@objectstack/plugin-sharing", - "@objectstack/plugin-sms", + "@objectstack/service-sms", "@objectstack/plugin-webhooks", "@objectstack/trigger-record-change", "@objectstack/trigger-api", diff --git a/.changeset/rename-plugin-sms-to-service-sms.md b/.changeset/rename-plugin-sms-to-service-sms.md new file mode 100644 index 0000000000..cb8ca089df --- /dev/null +++ b/.changeset/rename-plugin-sms-to-service-sms.md @@ -0,0 +1,15 @@ +--- +"@objectstack/service-sms": patch +"@objectstack/cli": patch +--- + +refactor(sms): rename `@objectstack/plugin-sms` to `@objectstack/service-sms` + +Infrastructure services follow the `service-*` convention +(`service-messaging`, `service-settings`, …) — the `plugin-*` prefix was a +misfit for a package whose whole job is registering the `sms` kernel +service (`plugin-email` is legacy debt, not precedent). Same exports, same +`SmsServicePlugin` class, same `sms` service id and settings namespace — +only the package name and its home (`packages/services/service-sms`) +change. The one published `@objectstack/plugin-sms@14.3.0` release should +be npm-deprecated in favour of `@objectstack/service-sms`. diff --git a/content/docs/plugins/packages.mdx b/content/docs/plugins/packages.mdx index 574ee1af20..806c2137a7 100644 --- a/content/docs/plugins/packages.mdx +++ b/content/docs/plugins/packages.mdx @@ -15,8 +15,8 @@ ObjectStack is organized into **71 package manifests** across multiple categorie | **Client / DX** | 5 | `client`, `client-react`, `cli`, `create-objectstack`, `vscode-objectstack` | | **Framework adapters** | 1 | `hono` (other frameworks: build a thin adapter on `HttpDispatcher` — see below) | | **Drivers** | 4 | `driver-memory`, `driver-sql`, `driver-sqlite-wasm`, `driver-mongodb` | -| **Plugins** | 19 | `plugin-auth`, `plugin-security`, `plugin-org-scoping`, `plugin-audit`, `plugin-approvals`, `plugin-sharing`, `plugin-email`, `plugin-sms`, `plugin-webhooks`, `plugin-reports`, `plugin-hono-server`, `plugin-dev`, `mcp`, trigger plugins (`trigger-api`, `trigger-record-change`, `trigger-schedule`), and knowledge/embedder plugins (`knowledge-memory`, `knowledge-ragflow`, `embedder-openai`) | -| **Platform services** | 15 | `service-analytics`, `service-automation`, `service-cache`, `service-cluster`, `service-cluster-redis`, `service-datasource`, `service-i18n`, `service-job`, `service-knowledge`, `service-messaging`, `service-package`, `service-queue`, `service-realtime`, `service-settings`, `service-storage` | +| **Plugins** | 18 | `plugin-auth`, `plugin-security`, `plugin-org-scoping`, `plugin-audit`, `plugin-approvals`, `plugin-sharing`, `plugin-email`, `plugin-webhooks`, `plugin-reports`, `plugin-hono-server`, `plugin-dev`, `mcp`, trigger plugins (`trigger-api`, `trigger-record-change`, `trigger-schedule`), and knowledge/embedder plugins (`knowledge-memory`, `knowledge-ragflow`, `embedder-openai`) | +| **Platform services** | 16 | `service-analytics`, `service-automation`, `service-cache`, `service-cluster`, `service-cluster-redis`, `service-datasource`, `service-i18n`, `service-job`, `service-knowledge`, `service-messaging`, `service-package`, `service-queue`, `service-realtime`, `service-settings`, `service-sms`, `service-storage` | ## Core Packages @@ -226,6 +226,13 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: `MessagingChannel` registry, `emit()` fan-out, always-on inbox channel; email/webhook/push/IM channels plug in - **When to use**: Notifying users across channels from flows, hooks, and plugins +### @objectstack/service-sms + +**SMS Service** — Outbound SMS delivery (`sms` service). + +- **Features**: Provider adapters (Aliyun SMS, Twilio), `sms` settings namespace with live rebind, backs phone-number OTP sign-in/reset and the messaging `sms` channel +- **When to use**: Phone OTP first-login / self-service reset, SMS notifications + ### @objectstack/service-i18n **I18n Service** — Internationalization with file-based locales. @@ -363,13 +370,6 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: Provider adapters, MJML templates, delivery tracking - **When to use**: Transactional and workflow-driven email -### @objectstack/plugin-sms - -**SMS Plugin** — Outbound SMS delivery (`sms` service). - -- **Features**: Provider adapters (Aliyun SMS, Twilio), `sms` settings namespace with live rebind, backs phone-number OTP sign-in/reset and the messaging `sms` channel -- **When to use**: Phone OTP first-login / self-service reset, SMS notifications - ### @objectstack/plugin-webhooks **Webhooks Plugin** — Outbound HTTP webhook delivery. diff --git a/packages/cli/package.json b/packages/cli/package.json index 979ff7f2f7..307db038e1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -62,7 +62,7 @@ "@objectstack/plugin-reports": "workspace:*", "@objectstack/plugin-security": "workspace:*", "@objectstack/plugin-sharing": "workspace:*", - "@objectstack/plugin-sms": "workspace:*", + "@objectstack/service-sms": "workspace:*", "@objectstack/plugin-webhooks": "workspace:*", "@objectstack/rest": "workspace:*", "@objectstack/runtime": "workspace:^", diff --git a/packages/cli/src/commands/serve.ts b/packages/cli/src/commands/serve.ts index c5136a4c4c..141e449dfd 100644 --- a/packages/cli/src/commands/serve.ts +++ b/packages/cli/src/commands/serve.ts @@ -1259,6 +1259,12 @@ export default class Serve extends Command { // operator override (env wins in buildPluginList()). passwordRejectBreached: String(process.env.OS_AUTH_PASSWORD_REJECT_BREACHED ?? 'false').toLowerCase() === 'true', + // #2766/#2780 — phone-number sign-in (phone+password always; + // OTP sign-in/reset once the sms capability has a deliverable + // provider). Opt-in: without this env the config flag had no + // `objectstack serve` switch at all. + phoneNumber: + String(process.env.OS_AUTH_PHONE_NUMBER_ENABLED ?? 'false').toLowerCase() === 'true', }, advanced: process.env.OS_COOKIE_DOMAIN ? ({ @@ -1666,9 +1672,9 @@ export default class Serve extends Command { // the messaging `sms` channel. Provider config lives in the `sms` // settings namespace (OS_SMS_* env keys win at the resolver); // unconfigured ⇒ dev LogSmsTransport (no real send). - pkg: '@objectstack/plugin-sms', + pkg: '@objectstack/service-sms', export: 'SmsServicePlugin', - nameMatch: ['plugin-sms', 'SmsServicePlugin'], + nameMatch: ['service-sms', 'SmsServicePlugin'], }, sharing: { pkg: '@objectstack/plugin-sharing', diff --git a/packages/plugins/plugin-auth/src/auth-manager.test.ts b/packages/plugins/plugin-auth/src/auth-manager.test.ts index 94699045f4..fc07116d91 100644 --- a/packages/plugins/plugin-auth/src/auth-manager.test.ts +++ b/packages/plugins/plugin-auth/src/auth-manager.test.ts @@ -1143,29 +1143,42 @@ describe('AuthManager', () => { expect(sms.sent[0].templateParams).toEqual({ code: '123456' }); }); - it('enforces the per-number cooldown (TOO_MANY_REQUESTS, no second SMS)', async () => { + it('enforces the per-number cooldown at ADMISSION (before-hook), not in sendOTP', async () => { const { manager, opts } = await bootOtp(); const sms = fakeSms(); manager.setSmsService(sms.service); - await opts.sendOTP({ phoneNumber: PHONE, code: '111111' }); - await expect(opts.sendOTP({ phoneNumber: PHONE, code: '222222' })) + // Admission guard: first request per number passes, immediate second 429s. + await manager.assertPhoneOtpSendAllowed(PHONE); + await expect(manager.assertPhoneOtpSendAllowed(PHONE)) .rejects.toThrow(/Too many verification codes/); - expect(sms.sent).toHaveLength(1); + + // The sendOTP callback itself must NOT re-guard: better-auth stores the + // fresh code BEFORE invoking it, so a rejection at that point would + // still rotate (void) the previously delivered code. Delivery always + // proceeds once a request was admitted. + await opts.sendOTP({ phoneNumber: PHONE, code: '111111' }); + await opts.sendOTP({ phoneNumber: PHONE, code: '222222' }); + expect(sms.sent).toHaveLength(2); }); - it('sendPasswordResetOTP shares the same guarded SMS path (cross-flow budget)', async () => { - const { manager, opts } = await bootOtp(); - const sms = fakeSms(); - manager.setSmsService(sms.service); + it('the admission budget spans both flows (send-otp + request-password-reset)', async () => { + const { manager } = await bootOtp(); + manager.setSmsService(fakeSms().service); - await opts.sendPasswordResetOTP({ phoneNumber: PHONE, code: '333333' }); - expect(sms.sent[0].body).toContain('333333'); - // The cooldown spans both flows — a reset send blocks an immediate sign-in send. - await expect(opts.sendOTP({ phoneNumber: PHONE, code: '444444' })) + // One admitted send (whatever the flow) blocks an immediate second one. + await manager.assertPhoneOtpSendAllowed(PHONE); + await expect(manager.assertPhoneOtpSendAllowed(PHONE)) .rejects.toThrow(/Too many verification codes/); }); + it('admission is a no-op while OTP is undeliverable (sendOTP fails loudly instead)', async () => { + const { manager } = await bootOtp(); + // No SMS service wired — the guard must not consume budget or throw. + await manager.assertPhoneOtpSendAllowed(PHONE); + await manager.assertPhoneOtpSendAllowed(PHONE); + }); + it('surfaces a failed SMS delivery WITHOUT the code in the error', async () => { const { manager, opts } = await bootOtp(); const sms = fakeSms({ failed: true }); @@ -1175,13 +1188,11 @@ describe('AuthManager', () => { .rejects.toSatisfy((e: Error) => /provider down/.test(e.message) && !e.message.includes('555555')); }); - it('honours phoneOtp knobs (cooldown off ⇒ back-to-back sends allowed)', async () => { - const { manager, opts } = await bootOtp({ phoneOtp: { cooldownSeconds: 0, maxPerHour: 0 } }); - const sms = fakeSms(); - manager.setSmsService(sms.service); - await opts.sendOTP({ phoneNumber: PHONE, code: '111111' }); - await opts.sendOTP({ phoneNumber: PHONE, code: '222222' }); - expect(sms.sent).toHaveLength(2); + it('honours phoneOtp knobs (cooldown off ⇒ back-to-back admissions allowed)', async () => { + const { manager } = await bootOtp({ phoneOtp: { cooldownSeconds: 0, maxPerHour: 0 } }); + manager.setSmsService(fakeSms().service); + await manager.assertPhoneOtpSendAllowed(PHONE); + await manager.assertPhoneOtpSendAllowed(PHONE); }); it('features.phoneNumberOtp requires plugin + deliverable SMS', async () => { diff --git a/packages/plugins/plugin-auth/src/auth-manager.ts b/packages/plugins/plugin-auth/src/auth-manager.ts index c295492a26..764ac36f00 100644 --- a/packages/plugins/plugin-auth/src/auth-manager.ts +++ b/packages/plugins/plugin-auth/src/auth-manager.ts @@ -327,7 +327,7 @@ export interface AuthManagerOptions extends Partial { * * Resolved lazily through {@link AuthManager.getSmsService}; safe to set * after construction. AuthPlugin wires this from the kernel service - * registry (`sms`, see `@objectstack/plugin-sms`) on `kernel:ready`. + * registry (`sms`, see `@objectstack/service-sms`) on `kernel:ready`. */ smsService?: ISmsService; @@ -818,6 +818,23 @@ export class AuthManager { // sees `userCount > 0` and the toggle is enforced again. hooks: { before: createAuthMiddleware(async (ctx: any) => { + // ── #2780: per-number OTP send guard (admission control) ───── + // MUST run BEFORE the phone-number endpoints: better-auth's + // send-otp handler stores a fresh code and only THEN invokes + // `sendOTP` — a guard that throws inside the callback would + // still rotate (invalidate) the previously delivered code, so a + // blocked resend (or an attacker spamming the endpoint) could + // keep voiding the user's valid OTP. Rejecting here leaves the + // stored code untouched. Applies uniformly to registered and + // unregistered numbers (no account-existence oracle). + if ( + ctx?.path === '/phone-number/send-otp' || + ctx?.path === '/phone-number/request-password-reset' + ) { + const phone = typeof ctx?.body?.phoneNumber === 'string' ? ctx.body.phoneNumber : ''; + await this.assertPhoneOtpSendAllowed(phone); + } + // ── ADR-0069 D1: password complexity (validator) ──────────── // better-auth enforces only min/max length; class-mix is custom. // Runs on the password-mutating endpoints; reads the candidate from @@ -2057,7 +2074,7 @@ export class AuthManager { * OTP callbacks and the SMS invite path (#2780). Mirrors * {@link setEmailService}: resolved lazily per send, so it is safe to set * after construction — AuthPlugin wires it on `kernel:ready` once - * `ctx.getService('sms')` (plugin-sms) resolves. + * `ctx.getService('sms')` (service-sms) resolves. */ setSmsService(sms: ISmsService | undefined): void { this.config.smsService = sms; @@ -2084,15 +2101,36 @@ export class AuthManager { return this._otpSendGuard; } + /** + * #2780 — admission check for the OTP send endpoints, called from the + * `hooks.before` middleware (see the `/phone-number/*` branch there for + * why it cannot live in the `sendOTP` callback). Consumes one unit of the + * per-number budget and throws TOO_MANY_REQUESTS when the cooldown / + * hourly cap is exhausted. No-op while OTP is undeliverable — the send + * callback then fails loudly with NOT_SUPPORTED instead. + */ + async assertPhoneOtpSendAllowed(phone: string): Promise { + if (!phone || !this.isPhoneOtpDeliverable()) return; + const decision = await this.getOtpSendGuard().checkAndRecord(phone); + if (!decision.ok) { + const { APIError } = await import('better-auth/api'); + throw new APIError('TOO_MANY_REQUESTS', { + message: `Too many verification codes requested for this phone number. Retry in ${decision.retryAfterSeconds ?? 60}s.`, + }); + } + } + /** * #2780 — deliver a phone OTP through the SMS service. * * Security posture (all named requirements of #2780): * - No SMS service ⇒ throw NOT_SUPPORTED (loud, like the pre-SMS wiring). - * - Per-number cooldown + hourly cap BEFORE the provider is called - * (SMS-pumping cost abuse); violations throw TOO_MANY_REQUESTS, which - * /phone-number/send-otp surfaces as an honest 429 while the - * request-password-reset route logs-and-200s (no enumeration oracle). + * - The per-number cooldown + hourly cap live in the `hooks.before` + * admission check, NOT here: better-auth stores the fresh code before + * invoking this callback, so a rejection at this point would still + * rotate (invalidate) the previously delivered code — letting a + * blocked resend or an endpoint-spamming attacker void a user's valid + * OTP. See the `/phone-number/*` branch in `hooks.before`. * - The code is embedded in the message body ONLY — it must never reach * a log line or an error message (the SmsService logs masked numbers * and statuses, never bodies). @@ -2108,13 +2146,6 @@ export class AuthManager { 'Phone sign-in is password-based (POST /sign-in/phone-number).', ); } - const decision = await this.getOtpSendGuard().checkAndRecord(phone); - if (!decision.ok) { - const { APIError } = await import('better-auth/api'); - throw new APIError('TOO_MANY_REQUESTS', { - message: `Too many verification codes requested for this phone number. Retry in ${decision.retryAfterSeconds ?? 60}s.`, - }); - } const otpCfg = this.config.phoneOtp ?? {}; const minutes = Math.max(1, Math.round((otpCfg.expiresIn ?? 300) / 60)); const result = await sms.send({ diff --git a/packages/services/service-messaging/src/messaging-service-plugin.ts b/packages/services/service-messaging/src/messaging-service-plugin.ts index 5ff337f012..7526c3a2c6 100644 --- a/packages/services/service-messaging/src/messaging-service-plugin.ts +++ b/packages/services/service-messaging/src/messaging-service-plugin.ts @@ -214,7 +214,7 @@ export class MessagingServicePlugin implements Plugin { }); // SMS channel (#2780): same pattern as email — register when an - // `sms` service (plugin-sms) is present at kernel:ready; absent + // `sms` service (service-sms) is present at kernel:ready; absent // sms ⇒ no channel, so a notify(channels:['sms']) reports "not // registered" rather than silently no-opping. const getSms = () => { diff --git a/packages/services/service-messaging/src/sms-channel.ts b/packages/services/service-messaging/src/sms-channel.ts index fd0742b97e..021458b504 100644 --- a/packages/services/service-messaging/src/sms-channel.ts +++ b/packages/services/service-messaging/src/sms-channel.ts @@ -16,7 +16,7 @@ import { import { USER_OBJECT } from './email-channel.js'; /** - * Structural view of the SMS service (`@objectstack/plugin-sms`'s + * Structural view of the SMS service (`@objectstack/service-sms`'s * `SmsService`), declared locally so service-messaging takes no runtime * dependency on it — the channel resolves whatever is registered under the * `sms` service and sends through this shape (mirrors `EmailSenderSurface` diff --git a/packages/services/service-settings/src/manifests/sms.manifest.ts b/packages/services/service-settings/src/manifests/sms.manifest.ts index 1cc5aba581..2bcbe2b56a 100644 --- a/packages/services/service-settings/src/manifests/sms.manifest.ts +++ b/packages/services/service-settings/src/manifests/sms.manifest.ts @@ -81,6 +81,6 @@ export const smsTestActionHandler: SettingsActionHandler = async ({ values }) => return { ok: true, severity: 'info', - message: `Configuration looks valid (provider=${provider}). Wire @objectstack/plugin-sms for actual delivery.`, + message: `Configuration looks valid (provider=${provider}). Wire @objectstack/service-sms for actual delivery.`, }; }; diff --git a/packages/plugins/plugin-sms/CHANGELOG.md b/packages/services/service-sms/CHANGELOG.md similarity index 94% rename from packages/plugins/plugin-sms/CHANGELOG.md rename to packages/services/service-sms/CHANGELOG.md index 9ee02ced3c..5bfb917f7b 100644 --- a/packages/plugins/plugin-sms/CHANGELOG.md +++ b/packages/services/service-sms/CHANGELOG.md @@ -1,4 +1,4 @@ -# @objectstack/plugin-sms +# @objectstack/service-sms ## 14.3.0 @@ -9,7 +9,7 @@ #2766 shipped phone+password sign-in but no OTP — the platform had no SMS delivery capability. This adds the missing infrastructure end to end: - - **New `@objectstack/plugin-sms`** — `ISmsService`/`ISmsTransport` contracts + - **New `@objectstack/service-sms`** — `ISmsService`/`ISmsTransport` contracts (spec) with Aliyun SMS (ACS3-HMAC-SHA256, template-based) and Twilio transports plus a dev log fallback. Configured through the new `sms` settings namespace (live provider rebind, encrypted secrets, send-test diff --git a/packages/plugins/plugin-sms/LICENSE b/packages/services/service-sms/LICENSE similarity index 100% rename from packages/plugins/plugin-sms/LICENSE rename to packages/services/service-sms/LICENSE diff --git a/packages/plugins/plugin-sms/package.json b/packages/services/service-sms/package.json similarity index 79% rename from packages/plugins/plugin-sms/package.json rename to packages/services/service-sms/package.json index df9133a76c..82f6ac8e97 100644 --- a/packages/plugins/plugin-sms/package.json +++ b/packages/services/service-sms/package.json @@ -1,8 +1,8 @@ { - "name": "@objectstack/plugin-sms", + "name": "@objectstack/service-sms", "version": "14.3.0", "license": "Apache-2.0", - "description": "SMS service plugin for ObjectStack — ISmsService + transport-pluggable outbound delivery (Aliyun / Twilio / log).", + "description": "SMS service for ObjectStack — ISmsService + transport-pluggable outbound delivery (Aliyun / Twilio / log).", "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { diff --git a/packages/plugins/plugin-sms/src/index.ts b/packages/services/service-sms/src/index.ts similarity index 100% rename from packages/plugins/plugin-sms/src/index.ts rename to packages/services/service-sms/src/index.ts diff --git a/packages/plugins/plugin-sms/src/sms-plugin.test.ts b/packages/services/service-sms/src/sms-plugin.test.ts similarity index 100% rename from packages/plugins/plugin-sms/src/sms-plugin.test.ts rename to packages/services/service-sms/src/sms-plugin.test.ts diff --git a/packages/plugins/plugin-sms/src/sms-plugin.ts b/packages/services/service-sms/src/sms-plugin.ts similarity index 100% rename from packages/plugins/plugin-sms/src/sms-plugin.ts rename to packages/services/service-sms/src/sms-plugin.ts diff --git a/packages/plugins/plugin-sms/src/sms-service.test.ts b/packages/services/service-sms/src/sms-service.test.ts similarity index 100% rename from packages/plugins/plugin-sms/src/sms-service.test.ts rename to packages/services/service-sms/src/sms-service.test.ts diff --git a/packages/plugins/plugin-sms/src/sms-service.ts b/packages/services/service-sms/src/sms-service.ts similarity index 100% rename from packages/plugins/plugin-sms/src/sms-service.ts rename to packages/services/service-sms/src/sms-service.ts diff --git a/packages/plugins/plugin-sms/src/transports/aliyun.ts b/packages/services/service-sms/src/transports/aliyun.ts similarity index 100% rename from packages/plugins/plugin-sms/src/transports/aliyun.ts rename to packages/services/service-sms/src/transports/aliyun.ts diff --git a/packages/plugins/plugin-sms/src/transports/index.ts b/packages/services/service-sms/src/transports/index.ts similarity index 100% rename from packages/plugins/plugin-sms/src/transports/index.ts rename to packages/services/service-sms/src/transports/index.ts diff --git a/packages/plugins/plugin-sms/src/transports/transports.test.ts b/packages/services/service-sms/src/transports/transports.test.ts similarity index 100% rename from packages/plugins/plugin-sms/src/transports/transports.test.ts rename to packages/services/service-sms/src/transports/transports.test.ts diff --git a/packages/plugins/plugin-sms/src/transports/twilio.ts b/packages/services/service-sms/src/transports/twilio.ts similarity index 100% rename from packages/plugins/plugin-sms/src/transports/twilio.ts rename to packages/services/service-sms/src/transports/twilio.ts diff --git a/packages/plugins/plugin-sms/tsconfig.json b/packages/services/service-sms/tsconfig.json similarity index 100% rename from packages/plugins/plugin-sms/tsconfig.json rename to packages/services/service-sms/tsconfig.json diff --git a/packages/spec/src/contracts/sms-service.ts b/packages/spec/src/contracts/sms-service.ts index a2865a2e6f..346a67cc26 100644 --- a/packages/spec/src/contracts/sms-service.ts +++ b/packages/spec/src/contracts/sms-service.ts @@ -4,7 +4,7 @@ * ISmsService — Outbound SMS Service Contract (#2780) * * Sends short text messages through a pluggable transport (Aliyun SMS, - * Twilio, etc.). Concrete implementations live in `@objectstack/plugin-sms`; + * Twilio, etc.). Concrete implementations live in `@objectstack/service-sms`; * provider integrations plug in as an `ISmsTransport`. * * Mirrors the shape of `IEmailService`/`IEmailTransport` (email-service.ts) @@ -71,7 +71,7 @@ export interface SmsTransportSendResult { } /** - * Pluggable SMS transport. plugin-sms ships a `LogSmsTransport` for + * Pluggable SMS transport. service-sms ships a `LogSmsTransport` for * development; production deployments configure a concrete provider * (Aliyun / Twilio) or inject their own implementation of this shape. * diff --git a/packages/spec/src/system/auth-config.zod.ts b/packages/spec/src/system/auth-config.zod.ts index 5af3f9f22c..faae17f4a5 100644 --- a/packages/spec/src/system/auth-config.zod.ts +++ b/packages/spec/src/system/auth-config.zod.ts @@ -107,7 +107,7 @@ export const AuthPluginConfigSchema = lazySchema(() => z.object({ * (`/phone-number/send-otp` + `/verify` for sign-in/verification, * `/phone-number/request-password-reset` + `/reset-password` for * self-service reset) additionally require a configured SMS delivery - * service (`@objectstack/plugin-sms`, #2780) and stay loudly NOT_SUPPORTED + * service (`@objectstack/service-sms`, #2780) and stay loudly NOT_SUPPORTED * without one. Employees without an email address are created with a * generated placeholder address (never a real recipient — see * placeholder-email.ts) and sign in with phone + password or phone OTP. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02672cf767..321626b5ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -394,9 +394,6 @@ importers: '@objectstack/plugin-sharing': specifier: workspace:* version: link:../plugins/plugin-sharing - '@objectstack/plugin-sms': - specifier: workspace:* - version: link:../plugins/plugin-sms '@objectstack/plugin-webhooks': specifier: workspace:* version: link:../plugins/plugin-webhooks @@ -436,6 +433,9 @@ importers: '@objectstack/service-settings': specifier: workspace:* version: link:../services/service-settings + '@objectstack/service-sms': + specifier: workspace:* + version: link:../services/service-sms '@objectstack/service-storage': specifier: workspace:* version: link:../services/service-storage @@ -1522,25 +1522,6 @@ importers: specifier: ^4.1.10 version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)) - packages/plugins/plugin-sms: - dependencies: - '@objectstack/core': - specifier: workspace:* - version: link:../../core - '@objectstack/spec': - specifier: workspace:* - version: link:../../spec - devDependencies: - '@types/node': - specifier: ^26.1.0 - version: 26.1.0 - typescript: - specifier: ^6.0.3 - version: 6.0.3 - vitest: - specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)) - packages/plugins/plugin-webhooks: dependencies: '@objectstack/core': @@ -2000,6 +1981,25 @@ importers: specifier: ^4.1.10 version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)) + packages/services/service-sms: + dependencies: + '@objectstack/core': + specifier: workspace:* + version: link:../../core + '@objectstack/spec': + specifier: workspace:* + version: link:../../spec + devDependencies: + '@types/node': + specifier: ^26.1.0 + version: 26.1.0 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + vitest: + specifier: ^4.1.10 + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)) + packages/services/service-storage: dependencies: '@aws-sdk/client-s3':