@myronsi/messenger-api 2.0.0-alpha.1.next.29 → 2.0.0-alpha.2

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,16 @@
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.0.0-alpha.2
6
+
7
+ Authentication details found while implementing the Go backend:
8
+
9
+ - Session IDs (`Session.id`, `session_id` path parameter) are UUIDs (`format: uuid`) instead of decimal IDs.
10
+ - `code` of `POST /auth/login/2fa` and `POST /me/2fa/disable` accepts an authenticator code or a recovery code.
11
+ - `POST /me/2fa/confirm` answers `200` with `recovery_codes` (shown once) instead of `204`.
12
+ - `POST /me/password` can answer `403` (wrong current password); `POST /me/2fa/confirm` and `/me/2fa/disable` can answer `409`.
13
+ - The refresh cookie is scoped to `/api/v2/auth/refresh`.
14
+
5
15
  ## 2.0.0-alpha.1
6
16
 
7
17
  First draft of API contract v2 for the Go backend (`/api/v2`), published as `@myronsi/messenger-api@2.0.0-alpha.1`:
package/README.md CHANGED
@@ -15,7 +15,7 @@ api/
15
15
  ├─ ws-events.d.ts WebSocket event types (from the JSON Schemas)
16
16
  ├─ ws-events.schema.json bundled JSON Schemas (used by the breaking-change check)
17
17
  ├─ ws-docs.json websocket.md and the examples (used by the PATCH check)
18
- └─ index.js export const API_VERSION = "2.0.0-alpha.1"
18
+ └─ index.js export const API_VERSION = "2.0.0-alpha.2"
19
19
  ```
20
20
 
21
21
  ## Use in the frontend
@@ -45,14 +45,13 @@ Change the contract first, in the same PR as the code that implements it. Bump `
45
45
 
46
46
  ## Go server
47
47
 
48
- The Go server code is generated from `openapi.yaml` with [oapi-codegen](https://github.com/oapi-codegen/oapi-codegen) (strict server) in the same PR as a contract change. The configuration is `oapi-codegen.yaml`; the generator was verified with oapi-codegen v2.8.0 (Go 1.25) against this document. Add this directive to the Go module and commit the generated file:
48
+ The Go server code is generated from `openapi.yaml` with [oapi-codegen](https://github.com/oapi-codegen/oapi-codegen) (strict server) in the same PR as a contract change. The configuration is `oapi-codegen.yaml`, the output is `internal/httpapi/api.gen.go`, and the `go:generate` directive lives in `internal/httpapi/generate.go`:
49
49
 
50
- ```go
51
- //go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.8.0 -config api/oapi-codegen.yaml api/openapi.yaml
50
+ ```sh
51
+ make generate # go generate ./...
52
52
  ```
53
53
 
54
- As soon as the repository has a `go.mod`, CI runs `go generate ./... && git diff --exit-code`.
55
-
54
+ When an operation is added to the contract, the build fails until it is added to `internal/httpapi/unimplemented.go` (which answers 501) or to a real implementation. CI runs `go generate ./... && git diff --exit-code`, so a stale generated file fails the build.
56
55
  ## Publishing
57
56
 
58
57
  See `docs/releasing.md`: `next` on every merge to `master` that changes `api/`, the stable version after a backend release when `info.version` is not on npm yet. Both use `npm publish` with Trusted Publishing, which also attaches provenance.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /* Generated from api/openapi.yaml 2.0.0-alpha.1. Do not edit. */
2
- export declare const API_VERSION: "2.0.0-alpha.1";
1
+ /* Generated from api/openapi.yaml 2.0.0-alpha.2. Do not edit. */
2
+ export declare const API_VERSION: "2.0.0-alpha.2";
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.0.0-alpha.1";
1
+ export const API_VERSION = "2.0.0-alpha.2";
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.0.0-alpha.1
4
+ version: 2.0.0-alpha.2
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
@@ -356,7 +356,7 @@ paths:
356
356
  tags: [me]
357
357
  operationId: changePassword
358
358
  summary: Change the password
359
- description: Revokes all other sessions.
359
+ description: "Revokes all other sessions. A wrong current password answers `403` with `code: invalid_credentials`."
360
360
  requestBody:
361
361
  required: true
362
362
  content:
@@ -368,6 +368,7 @@ paths:
368
368
  description: Password changed
369
369
  "400": { $ref: "#/components/responses/BadRequest" }
370
370
  "401": { $ref: "#/components/responses/Unauthorized" }
371
+ "403": { $ref: "#/components/responses/Forbidden" }
371
372
  "422": { $ref: "#/components/responses/ValidationFailed" }
372
373
  "429": { $ref: "#/components/responses/TooManyRequests" }
373
374
  /me/security:
@@ -463,7 +464,7 @@ paths:
463
464
  tags: [me]
464
465
  operationId: setupTwoFactor
465
466
  summary: Start two-factor setup
466
- description: Returns the secret and an `otpauth://` URI; two-factor stays off until confirmed.
467
+ description: "Returns the secret and an `otpauth://` URI; two-factor stays off until confirmed. A wrong password answers `403` with `code: invalid_credentials`; `409` when two-factor is already on."
467
468
  requestBody:
468
469
  required: true
469
470
  content:
@@ -488,6 +489,7 @@ paths:
488
489
  tags: [me]
489
490
  operationId: confirmTwoFactor
490
491
  summary: Turn two-factor on with a code
492
+ description: "Returns the recovery codes. They are shown only once, so store them safely."
491
493
  requestBody:
492
494
  required: true
493
495
  content:
@@ -495,10 +497,14 @@ paths:
495
497
  schema: { $ref: "#/components/schemas/TwoFactorCode" }
496
498
  responses:
497
499
  "426": { $ref: "#/components/responses/ClientOutdated" }
498
- "204":
500
+ "200":
499
501
  description: Two-factor authentication enabled
502
+ content:
503
+ application/json:
504
+ schema: { $ref: "#/components/schemas/TwoFactorRecoveryCodes" }
500
505
  "400": { $ref: "#/components/responses/BadRequest" }
501
506
  "401": { $ref: "#/components/responses/Unauthorized" }
507
+ "409": { $ref: "#/components/responses/Conflict" }
502
508
  "429": { $ref: "#/components/responses/TooManyRequests" }
503
509
  /me/2fa/disable:
504
510
  parameters:
@@ -508,6 +514,7 @@ paths:
508
514
  tags: [me]
509
515
  operationId: disableTwoFactor
510
516
  summary: Turn two-factor off
517
+ description: "A wrong password or code answers `403` (`invalid_credentials` or `invalid_two_factor_code`); `409` when two-factor is off."
511
518
  requestBody:
512
519
  required: true
513
520
  content:
@@ -520,6 +527,7 @@ paths:
520
527
  "400": { $ref: "#/components/responses/BadRequest" }
521
528
  "401": { $ref: "#/components/responses/Unauthorized" }
522
529
  "403": { $ref: "#/components/responses/Forbidden" }
530
+ "409": { $ref: "#/components/responses/Conflict" }
523
531
  "429": { $ref: "#/components/responses/TooManyRequests" }
524
532
  /me/privacy:
525
533
  parameters:
@@ -1512,7 +1520,7 @@ components:
1512
1520
 
1513
1521
  headers:
1514
1522
  RefreshCookie:
1515
- description: HttpOnly, Secure, SameSite refresh cookie scoped to `/api/v2/auth`. Login and refresh set it; logout clears it (expired cookie).
1523
+ description: HttpOnly, Secure, SameSite refresh cookie scoped to `/api/v2/auth/refresh`, so the browser sends it only to the refresh endpoint. Login and refresh set it; logout clears it (expired cookie).
1516
1524
  schema: { type: string }
1517
1525
 
1518
1526
  parameters:
@@ -1555,7 +1563,7 @@ components:
1555
1563
  name: session_id
1556
1564
  in: path
1557
1565
  required: true
1558
- schema: { $ref: "#/components/schemas/Id" }
1566
+ schema: { type: string, format: uuid }
1559
1567
 
1560
1568
  ClientVersion:
1561
1569
  name: X-Client-Version
@@ -1734,7 +1742,10 @@ components:
1734
1742
  required: [login_challenge, code]
1735
1743
  properties:
1736
1744
  login_challenge: { type: string }
1737
- code: { type: string, pattern: "^[0-9]{6}$" }
1745
+ code:
1746
+ type: string
1747
+ pattern: "^([0-9]{6}|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{8})$"
1748
+ description: A code of the authenticator app, or an unused recovery code.
1738
1749
  TokenResponse:
1739
1750
  type: object
1740
1751
  required: [access_token, token_type, expires_in]
@@ -1829,7 +1840,7 @@ components:
1829
1840
  type: object
1830
1841
  required: [id, created_at, last_used_at, is_current]
1831
1842
  properties:
1832
- id: { $ref: "#/components/schemas/Id" }
1843
+ id: { type: string, format: uuid }
1833
1844
  device:
1834
1845
  type: [string, "null"]
1835
1846
  description: Sanitised user agent
@@ -1848,12 +1859,23 @@ components:
1848
1859
  required: [code]
1849
1860
  properties:
1850
1861
  code: { type: string, pattern: "^[0-9]{6}$" }
1862
+ TwoFactorRecoveryCodes:
1863
+ type: object
1864
+ required: [recovery_codes]
1865
+ properties:
1866
+ recovery_codes:
1867
+ type: array
1868
+ items: { type: string }
1869
+ description: One-time codes for signing in without the authenticator app. Shown only once.
1851
1870
  DisableTwoFactorRequest:
1852
1871
  type: object
1853
1872
  required: [password, code]
1854
1873
  properties:
1855
1874
  password: { type: string, maxLength: 128, writeOnly: true }
1856
- code: { type: string, pattern: "^[0-9]{6}$" }
1875
+ code:
1876
+ type: string
1877
+ pattern: "^([0-9]{6}|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{8})$"
1878
+ description: A code of the authenticator app, or an unused recovery code.
1857
1879
  Visibility:
1858
1880
  type: string
1859
1881
  description: Who may see the item. `*_except` scopes use the exception lists.
package/dist/schema.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /* Generated from api/openapi.yaml 2.0.0-alpha.1. Do not edit. */
1
+ /* Generated from api/openapi.yaml 2.0.0-alpha.2. Do not edit. */
2
2
  export interface paths {
3
3
  "/meta": {
4
4
  parameters: {
@@ -276,7 +276,7 @@ export interface paths {
276
276
  get?: never;
277
277
  /**
278
278
  * Change the password
279
- * @description Revokes all other sessions.
279
+ * @description Revokes all other sessions. A wrong current password answers `403` with `code: invalid_credentials`.
280
280
  */
281
281
  put: operations["changePassword"];
282
282
  post?: never;
@@ -372,7 +372,7 @@ export interface paths {
372
372
  put?: never;
373
373
  /**
374
374
  * Start two-factor setup
375
- * @description Returns the secret and an `otpauth://` URI; two-factor stays off until confirmed.
375
+ * @description Returns the secret and an `otpauth://` URI; two-factor stays off until confirmed. A wrong password answers `403` with `code: invalid_credentials`; `409` when two-factor is already on.
376
376
  */
377
377
  post: operations["setupTwoFactor"];
378
378
  delete?: never;
@@ -395,7 +395,10 @@ export interface paths {
395
395
  };
396
396
  get?: never;
397
397
  put?: never;
398
- /** Turn two-factor on with a code */
398
+ /**
399
+ * Turn two-factor on with a code
400
+ * @description Returns the recovery codes. They are shown only once, so store them safely.
401
+ */
399
402
  post: operations["confirmTwoFactor"];
400
403
  delete?: never;
401
404
  options?: never;
@@ -417,7 +420,10 @@ export interface paths {
417
420
  };
418
421
  get?: never;
419
422
  put?: never;
420
- /** Turn two-factor off */
423
+ /**
424
+ * Turn two-factor off
425
+ * @description A wrong password or code answers `403` (`invalid_credentials` or `invalid_two_factor_code`); `409` when two-factor is off.
426
+ */
421
427
  post: operations["disableTwoFactor"];
422
428
  delete?: never;
423
429
  options?: never;
@@ -1286,6 +1292,7 @@ export interface components {
1286
1292
  };
1287
1293
  TwoFactorLoginRequest: {
1288
1294
  login_challenge: string;
1295
+ /** @description A code of the authenticator app, or an unused recovery code. */
1289
1296
  code: string;
1290
1297
  };
1291
1298
  TokenResponse: {
@@ -1344,7 +1351,8 @@ export interface components {
1344
1351
  session_duration_days: number;
1345
1352
  };
1346
1353
  Session: {
1347
- id: components["schemas"]["Id"];
1354
+ /** Format: uuid */
1355
+ id: string;
1348
1356
  /** @description Sanitised user agent */
1349
1357
  device?: string | null;
1350
1358
  created_at: components["schemas"]["Timestamp"];
@@ -1359,8 +1367,13 @@ export interface components {
1359
1367
  TwoFactorCode: {
1360
1368
  code: string;
1361
1369
  };
1370
+ TwoFactorRecoveryCodes: {
1371
+ /** @description One-time codes for signing in without the authenticator app. Shown only once. */
1372
+ recovery_codes: string[];
1373
+ };
1362
1374
  DisableTwoFactorRequest: {
1363
1375
  password: string;
1376
+ /** @description A code of the authenticator app, or an unused recovery code. */
1364
1377
  code: string;
1365
1378
  };
1366
1379
  /**
@@ -1717,7 +1730,7 @@ export interface components {
1717
1730
  UserId: components["schemas"]["Id"];
1718
1731
  RequestId: components["schemas"]["Id"];
1719
1732
  AttachmentId: components["schemas"]["Id"];
1720
- SessionId: components["schemas"]["Id"];
1733
+ SessionId: string;
1721
1734
  /** @description Version of the client app, for logs and the per-client metric. */
1722
1735
  ClientVersion: string;
1723
1736
  /** @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`). */
@@ -1725,7 +1738,7 @@ export interface components {
1725
1738
  };
1726
1739
  requestBodies: never;
1727
1740
  headers: {
1728
- /** @description HttpOnly, Secure, SameSite refresh cookie scoped to `/api/v2/auth`. Login and refresh set it; logout clears it (expired cookie). */
1741
+ /** @description HttpOnly, Secure, SameSite refresh cookie scoped to `/api/v2/auth/refresh`, so the browser sends it only to the refresh endpoint. Login and refresh set it; logout clears it (expired cookie). */
1729
1742
  RefreshCookie: string;
1730
1743
  };
1731
1744
  pathItems: never;
@@ -2161,6 +2174,7 @@ export interface operations {
2161
2174
  };
2162
2175
  400: components["responses"]["BadRequest"];
2163
2176
  401: components["responses"]["Unauthorized"];
2177
+ 403: components["responses"]["Forbidden"];
2164
2178
  422: components["responses"]["ValidationFailed"];
2165
2179
  426: components["responses"]["ClientOutdated"];
2166
2180
  429: components["responses"]["TooManyRequests"];
@@ -2365,14 +2379,17 @@ export interface operations {
2365
2379
  };
2366
2380
  responses: {
2367
2381
  /** @description Two-factor authentication enabled */
2368
- 204: {
2382
+ 200: {
2369
2383
  headers: {
2370
2384
  [name: string]: unknown;
2371
2385
  };
2372
- content?: never;
2386
+ content: {
2387
+ "application/json": components["schemas"]["TwoFactorRecoveryCodes"];
2388
+ };
2373
2389
  };
2374
2390
  400: components["responses"]["BadRequest"];
2375
2391
  401: components["responses"]["Unauthorized"];
2392
+ 409: components["responses"]["Conflict"];
2376
2393
  426: components["responses"]["ClientOutdated"];
2377
2394
  429: components["responses"]["TooManyRequests"];
2378
2395
  };
@@ -2405,6 +2422,7 @@ export interface operations {
2405
2422
  400: components["responses"]["BadRequest"];
2406
2423
  401: components["responses"]["Unauthorized"];
2407
2424
  403: components["responses"]["Forbidden"];
2425
+ 409: components["responses"]["Conflict"];
2408
2426
  426: components["responses"]["ClientOutdated"];
2409
2427
  429: components["responses"]["TooManyRequests"];
2410
2428
  };
package/dist/ws-docs.json CHANGED
@@ -334,7 +334,7 @@
334
334
  "event_id": "7217400317439950009",
335
335
  "chat_id": null,
336
336
  "data": {
337
- "api_version": "2.0.0-alpha.1",
337
+ "api_version": "2.0.0-alpha.2",
338
338
  "min_client_api_version": "2.0.0-alpha.1",
339
339
  "user_id": "1001"
340
340
  }
@@ -1,4 +1,4 @@
1
- /* Generated from api/openapi.yaml 2.0.0-alpha.1. Do not edit. */
1
+ /* Generated from api/openapi.yaml 2.0.0-alpha.2. Do not edit. */
2
2
 
3
3
  /**
4
4
  * Any WebSocket event, in either direction.
@@ -214,7 +214,8 @@
214
214
  },
215
215
  "code": {
216
216
  "type": "string",
217
- "pattern": "^[0-9]{6}$"
217
+ "pattern": "^([0-9]{6}|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{8})$",
218
+ "description": "A code of the authenticator app, or an unused recovery code."
218
219
  }
219
220
  }
220
221
  },
@@ -450,7 +451,8 @@
450
451
  ],
451
452
  "properties": {
452
453
  "id": {
453
- "$ref": "#/$defs/Id"
454
+ "type": "string",
455
+ "format": "uuid"
454
456
  },
455
457
  "device": {
456
458
  "type": [
@@ -500,6 +502,21 @@
500
502
  }
501
503
  }
502
504
  },
505
+ "TwoFactorRecoveryCodes": {
506
+ "type": "object",
507
+ "required": [
508
+ "recovery_codes"
509
+ ],
510
+ "properties": {
511
+ "recovery_codes": {
512
+ "type": "array",
513
+ "items": {
514
+ "type": "string"
515
+ },
516
+ "description": "One-time codes for signing in without the authenticator app. Shown only once."
517
+ }
518
+ }
519
+ },
503
520
  "DisableTwoFactorRequest": {
504
521
  "type": "object",
505
522
  "required": [
@@ -514,7 +531,8 @@
514
531
  },
515
532
  "code": {
516
533
  "type": "string",
517
- "pattern": "^[0-9]{6}$"
534
+ "pattern": "^([0-9]{6}|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{8})$",
535
+ "description": "A code of the authenticator app, or an unused recovery code."
518
536
  }
519
537
  }
520
538
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myronsi/messenger-api",
3
- "version": "2.0.0-alpha.1.next.29",
3
+ "version": "2.0.0-alpha.2",
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": {