@myronsi/messenger-api 2.1.0-alpha.15 → 2.1.0-alpha.16.next.89

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/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  Contract changes only. The backend changelog is `CHANGELOG.md` in the repository root. Rules: `docs/api-compatibility.md`.
4
4
 
5
+ ## 2.1.0-alpha.16
6
+
7
+ - Trusted-device recovery no longer needs the current password. `DeviceRecoveryApproval.password` is no longer
8
+ required, is deprecated and ignored. New optional `passkey` (`PasskeyVerification`).
9
+ - `POST /auth/recovery-requests/{requestId}/approve/options` (`DeviceRecoveryApprovalOptions`: `method` `passkey`
10
+ with `challenge_id` and `public_key`, or `session` with `approvable_from` while the session is too new).
11
+ - New error codes `user_verification_required` (account has passkeys, no assertion sent) and `session_too_new`
12
+ (no passkeys and the session is younger than the minimum session age), both `403` on the approval.
13
+
5
14
  ## 2.1.0-alpha.15
6
15
 
7
16
  - End-to-end encrypted audio calls in direct chats and groups (tag `calls`, see `docs/calls.md` and
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /* Generated from api/openapi.yaml 2.1.0-alpha.15. Do not edit. */
2
- export declare const API_VERSION: "2.1.0-alpha.15";
1
+ /* Generated from api/openapi.yaml 2.1.0-alpha.16. Do not edit. */
2
+ export declare const API_VERSION: "2.1.0-alpha.16";
3
3
  export type { paths, components, operations } from "./schema.js";
4
4
  export * from "./ws-events.js";
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export const API_VERSION = "2.1.0-alpha.15";
1
+ export const API_VERSION = "2.1.0-alpha.16";
package/dist/openapi.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  openapi: 3.1.0
2
2
  info:
3
3
  title: Messenger API
4
- version: 2.1.0-alpha.15
4
+ version: 2.1.0-alpha.16
5
5
  summary: REST contract of the Messenger backend (v2).
6
6
  description: |
7
7
  Contract for the Go backend. The WebSocket protocol is described in `websocket/` and
@@ -2172,7 +2172,22 @@ paths:
2172
2172
  tags:
2173
2173
  - auth
2174
2174
  operationId: approveDeviceRecovery
2175
- summary: Approve a recovery request using number matching, the password and the second factor
2175
+ summary: Approve a recovery request using number matching, user verification and the second factor
2176
+ description: |
2177
+ Approval does not need the current password, which the user may have forgotten. The checks run in this
2178
+ order: the request must belong to the caller and `match_number` must match (a wrong number denies the
2179
+ request), then user verification, then the second factor.
2180
+
2181
+ User verification follows `POST /auth/recovery-requests/{requestId}/approve/options`. When the account
2182
+ has passkeys, `passkey` must carry an assertion with user verification for the challenge from that call
2183
+ (`403 user_verification_required` without it, `403 invalid_credentials` for a failed assertion,
2184
+ `400 invalid_request` for an unknown, used or expired challenge). A missing `code` answers
2185
+ `401 two_factor_required` before the challenge is used, so the request can be repeated with the same
2186
+ assertion and the code. A wrong code is checked after the passkey and uses up the challenge.
2187
+ Without passkeys the approving session must be at least as old as the minimum session age
2188
+ (`RECOVERY_APPROVAL_MIN_SESSION_AGE`, 72 hours by default), otherwise `403 session_too_new`.
2189
+
2190
+ `password` is ignored and only accepted so that older clients keep working.
2176
2191
  requestBody:
2177
2192
  required: true
2178
2193
  content:
@@ -2201,6 +2216,41 @@ paths:
2201
2216
  $ref: "#/components/responses/ClientOutdated"
2202
2217
  "429":
2203
2218
  $ref: "#/components/responses/TooManyRequests"
2219
+ /auth/recovery-requests/{requestId}/approve/options:
2220
+ parameters:
2221
+ - *a1
2222
+ - *a2
2223
+ - *a4
2224
+ post:
2225
+ tags:
2226
+ - auth
2227
+ operationId: deviceRecoveryApprovalOptions
2228
+ summary: Tell the approving device how to verify its user
2229
+ description: |
2230
+ `method: passkey` when the account has passkeys: `challenge_id` and `public_key` (WebAuthn request
2231
+ options with `userVerification: required`) are for `navigator.credentials.get`, and the result goes to
2232
+ the approval as `passkey`. The challenge is bound to this session and request, is single use and expires
2233
+ after 5 minutes or with the request. `method: session` otherwise; `approvable_from` is set while the
2234
+ session is younger than the minimum session age and tells when it may approve.
2235
+ responses:
2236
+ "200":
2237
+ description: Success
2238
+ content:
2239
+ application/json:
2240
+ schema:
2241
+ $ref: "#/components/schemas/DeviceRecoveryApprovalOptions"
2242
+ "400":
2243
+ $ref: "#/components/responses/BadRequest"
2244
+ "401":
2245
+ $ref: "#/components/responses/Unauthorized"
2246
+ "403":
2247
+ $ref: "#/components/responses/Forbidden"
2248
+ "404":
2249
+ $ref: "#/components/responses/NotFound"
2250
+ "426":
2251
+ $ref: "#/components/responses/ClientOutdated"
2252
+ "429":
2253
+ $ref: "#/components/responses/TooManyRequests"
2204
2254
  /auth/recovery-requests/{requestId}/deny:
2205
2255
  parameters:
2206
2256
  - *a1
@@ -4020,6 +4070,8 @@ components:
4020
4070
  - invalid_client_version
4021
4071
  - blocked_by_user
4022
4072
  - approval_required
4073
+ - user_verification_required
4074
+ - session_too_new
4023
4075
  - calls_unavailable
4024
4076
  - calls_not_allowed
4025
4077
  - call_identity_required
@@ -4933,18 +4985,42 @@ components:
4933
4985
  type: object
4934
4986
  required:
4935
4987
  - match_number
4936
- - password
4937
4988
  properties:
4938
4989
  match_number:
4939
4990
  type: integer
4940
4991
  minimum: 10
4941
4992
  maximum: 99
4993
+ passkey:
4994
+ $ref: "#/components/schemas/PasskeyVerification"
4995
+ code:
4996
+ type: string
4997
+ maxLength: 128
4942
4998
  password:
4943
4999
  type: string
4944
5000
  maxLength: 1024
4945
- code:
5001
+ deprecated: true
5002
+ description: Ignored. Older clients still send it.
5003
+ DeviceRecoveryApprovalOptions:
5004
+ type: object
5005
+ required:
5006
+ - method
5007
+ properties:
5008
+ method:
5009
+ type: string
5010
+ enum: [passkey, session]
5011
+ challenge_id:
4946
5012
  type: string
5013
+ minLength: 32
4947
5014
  maxLength: 128
5015
+ description: "Present with `method: passkey`."
5016
+ public_key:
5017
+ type: object
5018
+ additionalProperties: true
5019
+ description: "WebAuthn request options, present with `method: passkey`."
5020
+ approvable_from:
5021
+ type: string
5022
+ format: date-time
5023
+ description: "Present with `method: session` while the session is too new to approve."
4948
5024
  DeviceRecoveryComplete:
4949
5025
  type: object
4950
5026
  required:
package/dist/schema.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /* Generated from api/openapi.yaml 2.1.0-alpha.15. Do not edit. */
1
+ /* Generated from api/openapi.yaml 2.1.0-alpha.16. Do not edit. */
2
2
  export interface paths {
3
3
  "/meta": {
4
4
  parameters: {
@@ -1687,7 +1687,23 @@ export interface paths {
1687
1687
  };
1688
1688
  get?: never;
1689
1689
  put?: never;
1690
- /** Approve a recovery request using number matching, the password and the second factor */
1690
+ /**
1691
+ * Approve a recovery request using number matching, user verification and the second factor
1692
+ * @description Approval does not need the current password, which the user may have forgotten. The checks run in this
1693
+ * order: the request must belong to the caller and `match_number` must match (a wrong number denies the
1694
+ * request), then user verification, then the second factor.
1695
+ *
1696
+ * User verification follows `POST /auth/recovery-requests/{requestId}/approve/options`. When the account
1697
+ * has passkeys, `passkey` must carry an assertion with user verification for the challenge from that call
1698
+ * (`403 user_verification_required` without it, `403 invalid_credentials` for a failed assertion,
1699
+ * `400 invalid_request` for an unknown, used or expired challenge). A missing `code` answers
1700
+ * `401 two_factor_required` before the challenge is used, so the request can be repeated with the same
1701
+ * assertion and the code. A wrong code is checked after the passkey and uses up the challenge.
1702
+ * Without passkeys the approving session must be at least as old as the minimum session age
1703
+ * (`RECOVERY_APPROVAL_MIN_SESSION_AGE`, 72 hours by default), otherwise `403 session_too_new`.
1704
+ *
1705
+ * `password` is ignored and only accepted so that older clients keep working.
1706
+ */
1691
1707
  post: operations["approveDeviceRecovery"];
1692
1708
  delete?: never;
1693
1709
  options?: never;
@@ -1695,6 +1711,38 @@ export interface paths {
1695
1711
  patch?: never;
1696
1712
  trace?: never;
1697
1713
  };
1714
+ "/auth/recovery-requests/{requestId}/approve/options": {
1715
+ parameters: {
1716
+ query?: never;
1717
+ header?: {
1718
+ /** @description Version of the client app, for logs and the per-client metric. */
1719
+ "X-Client-Version"?: components["parameters"]["ClientVersion"];
1720
+ /** @description Contract version the client was built with (`API_VERSION` of `@myronsi/messenger-api`). A different MAJOR or a version below `min_client_api_version` is answered with `426`; a malformed value with `400` (`invalid_client_version`). */
1721
+ "X-Client-Api-Version"?: components["parameters"]["ClientApiVersion"];
1722
+ };
1723
+ path: {
1724
+ /** @description Approval handle from the recovery_request event. It is different from the requester's capability and cannot poll or complete the request. */
1725
+ requestId: string;
1726
+ };
1727
+ cookie?: never;
1728
+ };
1729
+ get?: never;
1730
+ put?: never;
1731
+ /**
1732
+ * Tell the approving device how to verify its user
1733
+ * @description `method: passkey` when the account has passkeys: `challenge_id` and `public_key` (WebAuthn request
1734
+ * options with `userVerification: required`) are for `navigator.credentials.get`, and the result goes to
1735
+ * the approval as `passkey`. The challenge is bound to this session and request, is single use and expires
1736
+ * after 5 minutes or with the request. `method: session` otherwise; `approvable_from` is set while the
1737
+ * session is younger than the minimum session age and tells when it may approve.
1738
+ */
1739
+ post: operations["deviceRecoveryApprovalOptions"];
1740
+ delete?: never;
1741
+ options?: never;
1742
+ head?: never;
1743
+ patch?: never;
1744
+ trace?: never;
1745
+ };
1698
1746
  "/auth/recovery-requests/{requestId}/deny": {
1699
1747
  parameters: {
1700
1748
  query?: never;
@@ -2549,7 +2597,7 @@ export interface components {
2549
2597
  * codes must be treated like `internal_error`.
2550
2598
  * @enum {string}
2551
2599
  */
2552
- ErrorCode: "invalid_request" | "validation_failed" | "unauthenticated" | "invalid_credentials" | "two_factor_required" | "invalid_two_factor_code" | "token_expired" | "forbidden" | "not_found" | "conflict" | "already_exists" | "payload_too_large" | "unsupported_media_type" | "rate_limited" | "client_outdated" | "invalid_client_version" | "blocked_by_user" | "approval_required" | "calls_unavailable" | "calls_not_allowed" | "call_identity_required" | "call_full" | "call_ended" | "already_in_call" | "stale_epoch" | "internal_error";
2600
+ ErrorCode: "invalid_request" | "validation_failed" | "unauthenticated" | "invalid_credentials" | "two_factor_required" | "invalid_two_factor_code" | "token_expired" | "forbidden" | "not_found" | "conflict" | "already_exists" | "payload_too_large" | "unsupported_media_type" | "rate_limited" | "client_outdated" | "invalid_client_version" | "blocked_by_user" | "approval_required" | "user_verification_required" | "session_too_new" | "calls_unavailable" | "calls_not_allowed" | "call_identity_required" | "call_full" | "call_ended" | "already_in_call" | "stale_epoch" | "internal_error";
2553
2601
  /** @description RFC 9457 problem details. `426 Upgrade Required` (`client_outdated`) uses the same format. */
2554
2602
  Problem: {
2555
2603
  /**
@@ -3064,8 +3112,28 @@ export interface components {
3064
3112
  };
3065
3113
  DeviceRecoveryApproval: {
3066
3114
  match_number: number;
3067
- password: string;
3115
+ passkey?: components["schemas"]["PasskeyVerification"];
3068
3116
  code?: string;
3117
+ /**
3118
+ * @deprecated
3119
+ * @description Ignored. Older clients still send it.
3120
+ */
3121
+ password?: string;
3122
+ };
3123
+ DeviceRecoveryApprovalOptions: {
3124
+ /** @enum {string} */
3125
+ method: "passkey" | "session";
3126
+ /** @description Present with `method: passkey`. */
3127
+ challenge_id?: string;
3128
+ /** @description WebAuthn request options, present with `method: passkey`. */
3129
+ public_key?: {
3130
+ [key: string]: unknown;
3131
+ };
3132
+ /**
3133
+ * Format: date-time
3134
+ * @description Present with `method: session` while the session is too new to approve.
3135
+ */
3136
+ approvable_from?: string;
3069
3137
  };
3070
3138
  DeviceRecoveryComplete: {
3071
3139
  new_password: string;
@@ -6365,6 +6433,40 @@ export interface operations {
6365
6433
  429: components["responses"]["TooManyRequests"];
6366
6434
  };
6367
6435
  };
6436
+ deviceRecoveryApprovalOptions: {
6437
+ parameters: {
6438
+ query?: never;
6439
+ header?: {
6440
+ /** @description Version of the client app, for logs and the per-client metric. */
6441
+ "X-Client-Version"?: components["parameters"]["ClientVersion"];
6442
+ /** @description Contract version the client was built with (`API_VERSION` of `@myronsi/messenger-api`). A different MAJOR or a version below `min_client_api_version` is answered with `426`; a malformed value with `400` (`invalid_client_version`). */
6443
+ "X-Client-Api-Version"?: components["parameters"]["ClientApiVersion"];
6444
+ };
6445
+ path: {
6446
+ /** @description Approval handle from the recovery_request event. It is different from the requester's capability and cannot poll or complete the request. */
6447
+ requestId: string;
6448
+ };
6449
+ cookie?: never;
6450
+ };
6451
+ requestBody?: never;
6452
+ responses: {
6453
+ /** @description Success */
6454
+ 200: {
6455
+ headers: {
6456
+ [name: string]: unknown;
6457
+ };
6458
+ content: {
6459
+ "application/json": components["schemas"]["DeviceRecoveryApprovalOptions"];
6460
+ };
6461
+ };
6462
+ 400: components["responses"]["BadRequest"];
6463
+ 401: components["responses"]["Unauthorized"];
6464
+ 403: components["responses"]["Forbidden"];
6465
+ 404: components["responses"]["NotFound"];
6466
+ 426: components["responses"]["ClientOutdated"];
6467
+ 429: components["responses"]["TooManyRequests"];
6468
+ };
6469
+ };
6368
6470
  denyDeviceRecovery: {
6369
6471
  parameters: {
6370
6472
  query?: never;
@@ -1,4 +1,4 @@
1
- /* Generated from api/openapi.yaml 2.1.0-alpha.15. Do not edit. */
1
+ /* Generated from api/openapi.yaml 2.1.0-alpha.16. Do not edit. */
2
2
 
3
3
  /**
4
4
  * Any WebSocket event, in either direction.
@@ -111,6 +111,8 @@ export type ErrorCode =
111
111
  | "invalid_client_version"
112
112
  | "blocked_by_user"
113
113
  | "approval_required"
114
+ | "user_verification_required"
115
+ | "session_too_new"
114
116
  | "calls_unavailable"
115
117
  | "calls_not_allowed"
116
118
  | "call_identity_required"
@@ -202,6 +202,8 @@
202
202
  "invalid_client_version",
203
203
  "blocked_by_user",
204
204
  "approval_required",
205
+ "user_verification_required",
206
+ "session_too_new",
205
207
  "calls_unavailable",
206
208
  "calls_not_allowed",
207
209
  "call_identity_required",
@@ -2299,8 +2301,7 @@
2299
2301
  "DeviceRecoveryApproval": {
2300
2302
  "type": "object",
2301
2303
  "required": [
2302
- "match_number",
2303
- "password"
2304
+ "match_number"
2304
2305
  ],
2305
2306
  "properties": {
2306
2307
  "match_number": {
@@ -2308,13 +2309,49 @@
2308
2309
  "minimum": 10,
2309
2310
  "maximum": 99
2310
2311
  },
2311
- "password": {
2312
- "type": "string",
2313
- "maxLength": 1024
2312
+ "passkey": {
2313
+ "$ref": "#/$defs/PasskeyVerification"
2314
2314
  },
2315
2315
  "code": {
2316
2316
  "type": "string",
2317
2317
  "maxLength": 128
2318
+ },
2319
+ "password": {
2320
+ "type": "string",
2321
+ "maxLength": 1024,
2322
+ "deprecated": true,
2323
+ "description": "Ignored. Older clients still send it."
2324
+ }
2325
+ }
2326
+ },
2327
+ "DeviceRecoveryApprovalOptions": {
2328
+ "type": "object",
2329
+ "required": [
2330
+ "method"
2331
+ ],
2332
+ "properties": {
2333
+ "method": {
2334
+ "type": "string",
2335
+ "enum": [
2336
+ "passkey",
2337
+ "session"
2338
+ ]
2339
+ },
2340
+ "challenge_id": {
2341
+ "type": "string",
2342
+ "minLength": 32,
2343
+ "maxLength": 128,
2344
+ "description": "Present with `method: passkey`."
2345
+ },
2346
+ "public_key": {
2347
+ "type": "object",
2348
+ "additionalProperties": true,
2349
+ "description": "WebAuthn request options, present with `method: passkey`."
2350
+ },
2351
+ "approvable_from": {
2352
+ "type": "string",
2353
+ "format": "date-time",
2354
+ "description": "Present with `method: session` while the session is too new to approve."
2318
2355
  }
2319
2356
  }
2320
2357
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myronsi/messenger-api",
3
- "version": "2.1.0-alpha.15",
3
+ "version": "2.1.0-alpha.16.next.89",
4
4
  "description": "API contract of the Messenger backend: OpenAPI document, WebSocket event schemas and generated TypeScript types",
5
5
  "license": "MIT",
6
6
  "repository": {