@fleetless/contracts 6.0.0-next.1 → 6.0.0-next.3
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 +53 -3
- package/artifacts/openapi.json +306 -29
- package/artifacts/routes.json +123 -22
- package/artifacts/schema/app-invitation.schema.json +4 -11
- package/artifacts/schema/audit-query.schema.json +6 -0
- package/artifacts/schema/feedback-request.schema.json +34 -0
- package/artifacts/schema/feedback-response.schema.json +27 -0
- package/artifacts/schema/job-actor.schema.json +15 -1
- package/artifacts/schema/job-run-list-response.schema.json +15 -1
- package/artifacts/schema/job-run.schema.json +15 -1
- package/artifacts/schema/role-delete-query.schema.json +13 -0
- package/artifacts/schema/role-in-use-details.schema.json +28 -0
- package/artifacts/schema/role-list-response.schema.json +1 -1
- package/artifacts/schema/role-rename-request.schema.json +16 -0
- package/artifacts/schema/role.schema.json +1 -1
- package/dist/app-users.d.ts +3 -4
- package/dist/app-users.js +4 -5
- package/dist/apps.d.ts +38 -2
- package/dist/apps.js +46 -3
- package/dist/audit.d.ts +1 -0
- package/dist/audit.js +13 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +36 -8
- package/dist/feedback.d.ts +42 -0
- package/dist/feedback.js +35 -0
- package/dist/identity.d.ts +3 -5
- package/dist/identity.js +3 -5
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/jobs.d.ts +3 -0
- package/dist/jobs.js +16 -0
- package/dist/routes.d.ts +10 -3
- package/dist/routes.js +79 -23
- package/package.json +1 -1
package/dist/routes.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
import { appListResponse, appDeletionSummary, createAppRequest, createServerKeyResponse, app as appSchema, role, roleListResponse, rolePermissions, serverKeyListResponse, updateAppRequest, } from './apps.js';
|
|
2
|
+
import { appListResponse, appDeletionSummary, createAppRequest, createServerKeyResponse, app as appSchema, role, roleListResponse, roleDeleteQuery, rolePermissions, roleRenameRequest, serverKeyListResponse, updateAppRequest, } from './apps.js';
|
|
3
3
|
import { alertListResponse, orgAlertsQuery, orgFiringAlertsResponse } from './alerts.js';
|
|
4
4
|
import { asset, assetListResponse, assetsClearResponse, assetSyncRequest, assetSyncResponse, assetSyncStatus, missingAssetQuery } from './assets.js';
|
|
5
5
|
import { auditListResponse, auditQuery } from './audit.js';
|
|
6
|
+
import { feedbackRequest, feedbackResponse } from './feedback.js';
|
|
6
7
|
import { CLIENT_OIDC_CALLBACK_PATH, clientAcceptInvitationRequest, clientIdentity, clientLoginCodeRequest, clientLoginCodeVerifyRequest, clientLoginRequest, clientLogoutRequest, clientMcpInteraction, clientMcpInteractionDecisionResponse, clientOidcCallbackQuery, clientOidcExchangeRequest, clientOidcStartQuery, clientPasswordResetConfirmRequest, clientPasswordResetRequest, clientProviderListQuery, clientProviderListResponse, clientRefreshRequest, clientRegisterRequest, clientResendVerificationRequest, clientSignInResult, clientTwoFactorDisableRequest, clientTwoFactorSetupConfirmRequest, clientTwoFactorSetupConfirmResponse, clientTwoFactorSetupRequest, clientTwoFactorVerifyRequest, clientVerifyEmailRequest, mcpConsentGrantListResponse, } from './client-auth.js';
|
|
7
8
|
import { clientRobotListResponse } from './client-robots.js';
|
|
8
9
|
import { appAuthConfig, appInvitation, appInvitationListResponse, appMailTemplate, appMailTemplateListResponse, appOidcProvider, appOidcProviderListResponse, appUser, appUserListResponse, createAppInvitationRequest, createAppOidcProviderRequest, createAppUserRequest, mailOutcome, mailTemplatePreviewRequest, mailTemplatePreviewResponse, patchAppOidcProviderRequest, patchAppUserRequest, putAppAuthLookRequest, putAppAuthMcpRequest, putAppAuthRegistrationRequest, putAppAuthSignInRequest, putAppAuthUrlsRequest, putAppMailTemplateRequest, } from './app-users.js';
|
|
@@ -126,9 +127,16 @@ const CLIENT_GUARD = ['unauthorized', 'token_expired', 'token_revoked', 'forbidd
|
|
|
126
127
|
*
|
|
127
128
|
* Every step is a page the auth portal serves to itself: `audience:
|
|
128
129
|
* 'internal'`, no request schema — the handlers read form fields by hand — and
|
|
129
|
-
* HTML for a browser form post, JSON for a JSON caller.
|
|
130
|
-
*
|
|
131
|
-
*
|
|
130
|
+
* HTML for a browser form post, JSON for a JSON caller.
|
|
131
|
+
*
|
|
132
|
+
* **The browser-proof cookie binds the interaction to one browser**, and it
|
|
133
|
+
* is set by whichever of these comes first for the interaction: the email
|
|
134
|
+
* card (`GET <prefix>/interaction/:id`), `POST <prefix>/identify`, or `POST
|
|
135
|
+
* <prefix>/passkey/options`. The email card is what a browser normally opens
|
|
136
|
+
* first; the two steps set it for a caller that never loaded the page, so
|
|
137
|
+
* `Sign in with a passkey` works without an email step. Once the interaction
|
|
138
|
+
* is bound, every step — those three included — without the matching cookie
|
|
139
|
+
* renders the `wrong_browser` page. The interaction's ten
|
|
132
140
|
* minutes cover every step; only when the last one is done is anything
|
|
133
141
|
* minted. **For `/mcp/oauth`, "done" means the consent step**, as the
|
|
134
142
|
* password did before.
|
|
@@ -150,11 +158,11 @@ export function developerSignInRoutes(prefix) {
|
|
|
150
158
|
params: [], query: null, request: null, response, errors: ['rate_limited', ...errors], transport: 'http', notes,
|
|
151
159
|
});
|
|
152
160
|
return [
|
|
153
|
-
step('/identify', 'Takes the email address, mails a sign-in code and hands back the code step.', null, ['validation_error', 'token_spent'], 'The page answers `Check your email` **for every address**: a known one gets `Your Fleetless sign-in code`, six digits valid ten ' +
|
|
161
|
+
step('/identify', 'Takes the email address, mails a sign-in code and hands back the code step.', null, ['validation_error', 'token_spent', 'wrong_browser'], 'The page answers `Check your email` **for every address**: a known one gets `Your Fleetless sign-in code`, six digits valid ten ' +
|
|
154
162
|
'minutes; an unknown one gets a mail saying no Fleetless account uses it, with a link to sign up (or the waiting list while sign-up ' +
|
|
155
163
|
'is closed). So the page never reveals who has an account, and the address is trimmed and compared case-insensitively. A request ' +
|
|
156
164
|
'within sixty seconds of the last one for the same address renders the same page without a second mail. The browser-proof cookie is ' +
|
|
157
|
-
`set here. A browser form post gets the code card; a JSON caller gets \`{ "next": "${prefix}/code" }\`, which has no schema. A dead ` +
|
|
165
|
+
`set here when the interaction has none yet, and checked when it has. A browser form post gets the code card; a JSON caller gets \`{ "next": "${prefix}/code" }\`, which has no schema. A dead ` +
|
|
158
166
|
'interaction is `410 token_spent`.'),
|
|
159
167
|
step('/code', 'Checks the emailed code and finishes the sign-in, or hands back the second step.', oauthRedirectResponse, ['validation_error', 'token_spent', 'wrong_browser', 'invalid_code'], 'A wrong code renders the code card again with the attempts left (`400 invalid_code`, `details.attempts_left`); a code spent, past ' +
|
|
160
168
|
'its ten minutes or out of its five attempts is `410 token_spent` and a new one has to be asked for. Spaces inside a typed code are ' +
|
|
@@ -171,7 +179,9 @@ export function developerSignInRoutes(prefix) {
|
|
|
171
179
|
`a \`303\` to ${done}; a JSON caller that URL as \`redirect_to\`.`),
|
|
172
180
|
step('/passkey/options', 'Answers the WebAuthn request options for a passkey sign-in or second step.', webauthnOptionsResponse, ['token_spent', 'wrong_browser'], 'Before an address is known the options name no credential, so the browser offers every discoverable passkey for `fleetless.dev` ' +
|
|
173
181
|
'(`Sign in with a passkey`); after the code step they name the account\'s own passkeys. User verification is required. The challenge ' +
|
|
174
|
-
'is bound to the interaction and single-use.'
|
|
182
|
+
'is bound to the interaction and single-use. **This is the first step of a passkey sign-in**, which has no email step: the ' +
|
|
183
|
+
'browser-proof cookie is set here when the interaction has none yet, and checked when it has — so `POST ' + prefix + '/passkey` ' +
|
|
184
|
+
'can require it.'),
|
|
175
185
|
step('/passkey', 'Checks a passkey assertion; a passkey completes the sign-in on its own.', oauthRedirectResponse, ['validation_error', 'token_spent', 'wrong_browser', 'invalid_credentials'], '**A passkey is a full sign-in**: it proves possession and user verification, two factors, so it skips the emailed code and the ' +
|
|
176
186
|
'second step — used as the second step, it finishes it. An assertion that does not verify, or names no passkey of an account, is ' +
|
|
177
187
|
'`401 invalid_credentials`, the same answer for both. When the organisation requires two-factor, a passkey satisfies it. A browser ' +
|
|
@@ -516,7 +526,7 @@ export const ROUTES = [
|
|
|
516
526
|
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }],
|
|
517
527
|
query: null, request: null, response: role,
|
|
518
528
|
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'not_found', 'validation_error'], transport: 'http',
|
|
519
|
-
notes: 'The body is `{ "name": string }` — non-empty, trimmed, at most
|
|
529
|
+
notes: 'The body is `{ "name": string }` — non-empty, trimmed, at most 60 characters as on `role.name` — and is deliberately not a contract shape: contracts ' +
|
|
520
530
|
'define the `role` this answers with, not this one trivial request. **The answer is a bare `role`, not an envelope**, unlike the ' +
|
|
521
531
|
'listing beside it.',
|
|
522
532
|
},
|
|
@@ -569,6 +579,33 @@ export const ROUTES = [
|
|
|
569
579
|
'offered and consults nothing about any user\'s actual MCP entitlement. A robot the role grants nothing on still appears, with an empty ' +
|
|
570
580
|
'`exposures` — dropping it would read as "not attached", which is a different fact.',
|
|
571
581
|
},
|
|
582
|
+
{
|
|
583
|
+
method: 'PATCH', path: '/api/apps/:id/roles/:roleId', section: 'apps',
|
|
584
|
+
summary: 'Renames a role; its users keep it.',
|
|
585
|
+
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: false, status: 200,
|
|
586
|
+
params: [
|
|
587
|
+
{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' },
|
|
588
|
+
{ name: 'roleId', description: 'The role\'s uuid, from `GET /api/apps/:id/roles`; a role of another app answers `404`.' },
|
|
589
|
+
],
|
|
590
|
+
query: null, request: roleRenameRequest, response: role,
|
|
591
|
+
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'not_found', 'validation_error', 'role_name_taken'], transport: 'http',
|
|
592
|
+
notes: 'Names are unique per app, compared exactly as stored after trimming. Built-in roles can be renamed.',
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
method: 'DELETE', path: '/api/apps/:id/roles/:roleId', section: 'apps',
|
|
596
|
+
summary: 'Deletes a role, moving its users, pending invitations and default-role status to another role.',
|
|
597
|
+
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: false, status: 204,
|
|
598
|
+
params: [
|
|
599
|
+
{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' },
|
|
600
|
+
{ name: 'roleId', description: 'The role\'s uuid, from `GET /api/apps/:id/roles`; a role of another app answers `404`.' },
|
|
601
|
+
],
|
|
602
|
+
query: roleDeleteQuery, request: null, response: null,
|
|
603
|
+
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'not_found', 'validation_error', 'role_in_use', 'last_role'], transport: 'http',
|
|
604
|
+
notes: 'Without `move_to`, a role that app users or pending invitations hold, or that is the app\'s default, answers ' +
|
|
605
|
+
'`409 role_in_use` with `{ users, invitations, is_default }`. With `move_to` — another role of the same app, else ' +
|
|
606
|
+
'`400 validation_error` — one transaction moves `app_users.role_id`, pending invitations and `default_role_id`, then deletes ' +
|
|
607
|
+
'the role and its permissions. The app\'s only role answers `409 last_role`. Built-in roles can be deleted like any other.',
|
|
608
|
+
},
|
|
572
609
|
{
|
|
573
610
|
method: 'POST', path: '/api/apps/:id/server-keys', section: 'apps',
|
|
574
611
|
summary: 'Mints a server key for the app and returns the raw secret once.',
|
|
@@ -696,8 +733,9 @@ export const ROUTES = [
|
|
|
696
733
|
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: false, status: 202,
|
|
697
734
|
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }, { name: 'userId', description: 'The app user\'s uuid, from `GET /api/apps/:id/users`; a user of another app answers `404`.' }],
|
|
698
735
|
query: null, request: null, response: mailOutcome,
|
|
699
|
-
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'not_found', 'target_state_conflict'], transport: 'http',
|
|
700
|
-
notes: 'The support door beside `POST /api/client/password/reset
|
|
736
|
+
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'not_found', 'target_state_conflict', 'method_not_allowed'], transport: 'http',
|
|
737
|
+
notes: 'The support door beside `POST /api/client/password/reset`, refused like it with `403 method_not_allowed` while the app has the ' +
|
|
738
|
+
'password method off: the same one-hour token and the same link, triggered by a developer for a ' +
|
|
701
739
|
'user who asked them rather than the form. **No enumeration discipline applies** — the caller is authenticated into the app and can read ' +
|
|
702
740
|
'the user list — so this one answers what actually happened: `{ "mail": mailStatus }`, where `not_configured` is a deployment without a ' +
|
|
703
741
|
'mailer and `failed` is the state worth somebody\'s attention. The link points at the app\'s `reset_url`, or at the hosted reset page ' +
|
|
@@ -1018,7 +1056,7 @@ export const ROUTES = [
|
|
|
1018
1056
|
method: 'GET', path: '/api/apps/:id/mail-templates/:kind', section: 'apps',
|
|
1019
1057
|
summary: 'Reads one custom mail template of the app.',
|
|
1020
1058
|
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: false, status: 200,
|
|
1021
|
-
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }, { name: 'kind', description: 'Which of the
|
|
1059
|
+
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }, { name: 'kind', description: 'Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.' }],
|
|
1022
1060
|
query: null, request: null, response: appMailTemplate,
|
|
1023
1061
|
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'validation_error', 'not_found'], transport: 'http',
|
|
1024
1062
|
notes: 'The `kind` segment is a `mailTemplateKind`, so a fourth word is `400 validation_error` — the path names a set that is closed, and ' +
|
|
@@ -1030,7 +1068,7 @@ export const ROUTES = [
|
|
|
1030
1068
|
method: 'PUT', path: '/api/apps/:id/mail-templates/:kind', section: 'apps',
|
|
1031
1069
|
summary: 'Stores or replaces the app\'s template for one kind of mail, refusing one that does not render.',
|
|
1032
1070
|
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: false, status: 200,
|
|
1033
|
-
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }, { name: 'kind', description: 'Which of the
|
|
1071
|
+
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }, { name: 'kind', description: 'Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.' }],
|
|
1034
1072
|
query: null, request: putAppMailTemplateRequest, response: appMailTemplate,
|
|
1035
1073
|
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'validation_error', 'not_found', 'template_invalid', 'rate_limited'], transport: 'http',
|
|
1036
1074
|
notes: 'The body carries `subject`, `text` and an optional `html`, each a Liquid template; `kind` is in the path and `updated_at` is the ' +
|
|
@@ -1048,7 +1086,7 @@ export const ROUTES = [
|
|
|
1048
1086
|
method: 'DELETE', path: '/api/apps/:id/mail-templates/:kind', section: 'apps',
|
|
1049
1087
|
summary: 'Drops the app\'s custom template for one kind, returning that mail to the Fleetless default.',
|
|
1050
1088
|
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: false, status: 204,
|
|
1051
|
-
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }, { name: 'kind', description: 'Which of the
|
|
1089
|
+
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }, { name: 'kind', description: 'Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.' }],
|
|
1052
1090
|
query: null, request: null, response: null,
|
|
1053
1091
|
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'validation_error', 'not_found'], transport: 'http',
|
|
1054
1092
|
notes: 'The mail keeps being sent — this removes the developer\'s wording, not the message. A kind that already has no custom template answers ' +
|
|
@@ -1059,7 +1097,7 @@ export const ROUTES = [
|
|
|
1059
1097
|
method: 'POST', path: '/api/apps/:id/mail-templates/:kind/preview', section: 'apps',
|
|
1060
1098
|
summary: 'Renders a template with sample data and answers the three parts, storing nothing.',
|
|
1061
1099
|
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: false, status: 200,
|
|
1062
|
-
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }, { name: 'kind', description: 'Which of the
|
|
1100
|
+
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }, { name: 'kind', description: 'Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.' }],
|
|
1063
1101
|
query: null, request: mailTemplatePreviewRequest, response: mailTemplatePreviewResponse,
|
|
1064
1102
|
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'validation_error', 'not_found', 'template_invalid', 'rate_limited'], transport: 'http',
|
|
1065
1103
|
notes: 'Takes the same document the PUT does and writes nothing, so a developer can see the rendered subject, text and HTML before anybody ' +
|
|
@@ -1076,7 +1114,7 @@ export const ROUTES = [
|
|
|
1076
1114
|
method: 'POST', path: '/api/apps/:id/mail-templates/:kind/test', section: 'apps',
|
|
1077
1115
|
summary: 'Sends the rendered template as a real mail to the calling developer.',
|
|
1078
1116
|
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: false, status: 202,
|
|
1079
|
-
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }, { name: 'kind', description: 'Which of the
|
|
1117
|
+
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }, { name: 'kind', description: 'Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.' }],
|
|
1080
1118
|
query: null, request: mailTemplatePreviewRequest, response: mailOutcome,
|
|
1081
1119
|
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'validation_error', 'not_found', 'template_invalid', 'rate_limited', 'target_state_conflict'],
|
|
1082
1120
|
transport: 'http',
|
|
@@ -1296,7 +1334,8 @@ export const ROUTES = [
|
|
|
1296
1334
|
query: null, request: null, response: null, errors: [], transport: 'http',
|
|
1297
1335
|
notes: 'HTML, and a GET rather than the body of the authorize response — so it is reloadable, bookmarkable and survives a back button, which ' +
|
|
1298
1336
|
'the inline page it replaced was not. An expired, consumed, unknown or hand-edited interaction renders one page at `410`, and so does a ' +
|
|
1299
|
-
'client whose dynamic registration lapsed in between.'
|
|
1337
|
+
'client whose dynamic registration lapsed in between. It offers the email field and `Sign in with a passkey`, and opening it binds ' +
|
|
1338
|
+
'the interaction to this browser with the browser-proof cookie, which every sign-in step after it checks.',
|
|
1300
1339
|
},
|
|
1301
1340
|
...developerSignInRoutes('/mcp/oauth'),
|
|
1302
1341
|
{
|
|
@@ -1347,7 +1386,8 @@ export const ROUTES = [
|
|
|
1347
1386
|
audience: 'internal', auth: 'none', rateLimited: false, ownerTier: false, status: 200,
|
|
1348
1387
|
params: [{ name: 'id', description: 'The interaction id minted by `GET /console/oauth/authorize`, which redirects the browser here.' }],
|
|
1349
1388
|
query: null, request: null, response: null, errors: [], transport: 'http',
|
|
1350
|
-
notes: 'HTML: the email field, `Email me a code`, and `Sign in with a passkey`.
|
|
1389
|
+
notes: 'HTML: the email field, `Email me a code`, and `Sign in with a passkey`. Opening it binds the interaction to this browser with the ' +
|
|
1390
|
+
'browser-proof cookie, which every sign-in step after it checks. An expired, consumed, unknown or hand-edited interaction ' +
|
|
1351
1391
|
'renders one page at `410`: which of the four it was is not a fact a stranger may learn, and to the person it is one fact anyway. The ' +
|
|
1352
1392
|
'page resolves nothing about the address typed into it, so there is no enumeration oracle here at all.',
|
|
1353
1393
|
},
|
|
@@ -1613,7 +1653,8 @@ export const ROUTES = [
|
|
|
1613
1653
|
errors: ['rate_limited', 'validation_error', 'not_found', 'method_not_allowed'], transport: 'http',
|
|
1614
1654
|
notes: '**`202` and an empty body for every request the policy allows**, in status, body and timing, whether or not the address names an ' +
|
|
1615
1655
|
'active account of this app — a decoy like `POST /api/client/resend-verification`, so this is no enumeration oracle. A mail goes out ' +
|
|
1616
|
-
'
|
|
1656
|
+
'for an `active` account and for one still `pending_verification` — spending the code proves the address, as the verification link ' +
|
|
1657
|
+
'would — and never for a `blocked` one or an unknown address. The code is six digits, valid ten minutes, takes five wrong attempts, and a new request expires ' +
|
|
1617
1658
|
'the previous one for the same address; a request within sixty seconds of the last sends no second mail. The address is trimmed and ' +
|
|
1618
1659
|
'compared case-insensitively. `404 not_found` is the **app identifier**, never the address; `403 method_not_allowed` when the app has ' +
|
|
1619
1660
|
'the email-code method off. Limited per app, address and IP, so it cannot be used to mail somebody repeatedly.',
|
|
@@ -1697,8 +1738,10 @@ export const ROUTES = [
|
|
|
1697
1738
|
summary: 'Mails an app user a reset link, and answers the same either way.',
|
|
1698
1739
|
audience: 'client', auth: 'none', rateLimited: true, ownerTier: false, status: 202,
|
|
1699
1740
|
params: [], query: null, request: clientPasswordResetRequest, response: null,
|
|
1700
|
-
errors: ['rate_limited', 'validation_error', 'not_found'], transport: 'http',
|
|
1741
|
+
errors: ['rate_limited', 'validation_error', 'not_found', 'method_not_allowed'], transport: 'http',
|
|
1701
1742
|
notes: 'The pair of app identifier and address is the identifier: an app user\'s address is unique only within their app. ' +
|
|
1743
|
+
'`403 method_not_allowed` when the app has the password method off — a reset link whose confirmation would be refused is not mailed; ' +
|
|
1744
|
+
'the code names the app\'s policy, not a person. ' +
|
|
1702
1745
|
'Status, body and timing are identical for a known and an unknown address. An account with no password — one created through an ' +
|
|
1703
1746
|
'identity provider — is mailed nothing and still answers `202`. `404 not_found` is the **app identifier**, never the address. The link ' +
|
|
1704
1747
|
'points at the app\'s `reset_url`, or at the hosted reset page when the app has configured none.',
|
|
@@ -1773,8 +1816,10 @@ export const ROUTES = [
|
|
|
1773
1816
|
summary: "Changes an app user's own password and answers a fresh session.",
|
|
1774
1817
|
audience: 'client', auth: 'developer_or_client', rateLimited: false, ownerTier: false, status: 200,
|
|
1775
1818
|
params: [], query: null, request: passwordChangeRequest, response: sessionTokens,
|
|
1776
|
-
errors: [...CLIENT_GUARD, 'validation_error', 'invalid_credentials', 'target_state_conflict'], transport: 'http',
|
|
1777
|
-
notes: '
|
|
1819
|
+
errors: [...CLIENT_GUARD, 'validation_error', 'invalid_credentials', 'target_state_conflict', 'method_not_allowed'], transport: 'http',
|
|
1820
|
+
notes: '`403 method_not_allowed` when the app has the password method off: a stored password stays stored but is not in use, so it is not ' +
|
|
1821
|
+
'changed either. ' +
|
|
1822
|
+
'The guard admits all three caller kinds, but a password belongs to an app user specifically — a developer bearer or a server key ' +
|
|
1778
1823
|
'reaching this is `401 unauthorized`. Every other session of the account ends; the answer is the replacement pair, so the tab that made ' +
|
|
1779
1824
|
'the change stays signed in. An app user belongs to one app, so "every session" is this app\'s. An account that has **no password** — ' +
|
|
1780
1825
|
'an OIDC-only app user, which the schema admits — answers `409 target_state_conflict` naming the `password` field with rule `not_set`, ' +
|
|
@@ -1806,10 +1851,11 @@ export const ROUTES = [
|
|
|
1806
1851
|
method: 'POST', path: '/api/client/two-factor/setup', section: 'client-auth',
|
|
1807
1852
|
summary: 'Starts an authenticator setup and answers its secret and otpauth URL.',
|
|
1808
1853
|
audience: 'client', auth: 'in_handler', rateLimited: true, ownerTier: false, status: 200,
|
|
1809
|
-
params: [], query: null, request: clientTwoFactorSetupRequest, response: twoFactorSetupResponse,
|
|
1854
|
+
params: [], query: null, request: clientTwoFactorSetupRequest, requestOptional: true, response: twoFactorSetupResponse,
|
|
1810
1855
|
errors: ['rate_limited', 'validation_error', 'token_spent', 'unauthorized', 'target_state_conflict'], transport: 'http',
|
|
1811
1856
|
notes: '**Two ways in, decided in the handler.** During sign-in the body carries the `two_factor_setup_required` challenge, and that is the ' +
|
|
1812
|
-
'credential; from the app\'s own account settings the app user\'s bearer is, with no challenge
|
|
1857
|
+
'credential; from the app\'s own account settings the app user\'s bearer is, with no challenge and an empty or missing body. Neither ' +
|
|
1858
|
+
'is `401 unauthorized`, and a ' +
|
|
1813
1859
|
'dead challenge is `410 token_spent`. `409 target_state_conflict` names `two_factor` with rule `off` when the app\'s policy is `off`. ' +
|
|
1814
1860
|
'The secret is not in use until `POST /api/client/two-factor/setup/confirm` accepts a code from it; a second call replaces a pending ' +
|
|
1815
1861
|
'secret, and an account that already has an authenticator keeps it until the new one is confirmed.',
|
|
@@ -2731,6 +2777,16 @@ export const ROUTES = [
|
|
|
2731
2777
|
'platform will answer is owed a refusal, not a shorter answer they will mistake for the whole picture. `from_day <= to_day` is a ' +
|
|
2732
2778
|
'cross-field rule no JSON Schema can express and is enforced here. The window is echoed back.',
|
|
2733
2779
|
},
|
|
2780
|
+
{
|
|
2781
|
+
method: 'POST', path: '/api/feedback', section: 'org',
|
|
2782
|
+
summary: 'Sends a message from a developer to the people who build Fleetless.',
|
|
2783
|
+
audience: 'developer', auth: 'developer', rateLimited: true, ownerTier: false, status: 202,
|
|
2784
|
+
params: [], query: null, request: feedbackRequest, response: feedbackResponse,
|
|
2785
|
+
errors: [...DEVELOPER_GUARD, 'validation_error', 'rate_limited'], transport: 'http',
|
|
2786
|
+
notes: 'The message is stored before any mail is tried, so `202` means it is kept whatever `mail` says: `sent`, `failed`, or ' +
|
|
2787
|
+
'`not_configured` when this cloud has no feedback address. At most 10 messages per developer per hour; the 11th answers ' +
|
|
2788
|
+
'`429 rate_limited` with `retry_after_ms`. Replies come by mail, to the sender\'s address.',
|
|
2789
|
+
},
|
|
2734
2790
|
/* ------------------------------------------------- assets (robot upload) */
|
|
2735
2791
|
{
|
|
2736
2792
|
method: 'POST', path: '/api/bridge/assets', section: 'assets',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetless/contracts",
|
|
3
|
-
"version": "6.0.0-next.
|
|
3
|
+
"version": "6.0.0-next.3",
|
|
4
4
|
"description": "Fleetless wire contracts: the bridge-cloud protocol, the REST API schemas and the error codes, as zod schemas with generated JSON Schema and OpenAPI artifacts.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Dehne Robotik GmbH",
|