Introduction
Administration of the Portals collection - the hostname allowlist that
decides which origins may authenticate at all, and which WebAuthn relying party (rpId) each of
them belongs to. See webauthn for what a relying party and a related origin are.
All four endpoints require access level ADMIN (security.token.minAdmin). Every write
invalidates the in-process portal cache immediately, rather than waiting for its normal 60 second
refresh.
List
Response
{ portals: [ /* full Portal documents, sorted by hostname */ ] }
Create
| URL | https://api.telecomx.dk/tools/portals |
| Method | POST |
| Header | Authorization | String | Bearer token |
| Body | JSON object |
| hostname | String | Required, 3-120 characters, lowercased, ^[a-z0-9.-]+(:\d+)?$. Host part of the Origin header this portal answers to, port included when non-default |
| customer | Id, null | Optional. Owning reseller; omit or null for a Telecom X-owned portal |
| name | String | Required, 1-60 characters. Shown as the WebAuthn relying party name in the OS prompt |
| rpId | String | Required, 3-120 characters, lowercased, ^[a-z0-9.-]+$. The relying party this hostname authenticates against |
| relatedOrigin | Boolean | Required. True to publish this hostname in the anchor's /.well-known/webauthn |
| enrollUrl | String | Required, a URL, max 300 characters. Enrollment link template with {token} and {lang} placeholders |
| state | String | Required. ACTIVE or DISABLED |
Body example
{
hostname: 'admin.xint.dk',
customer: null,
name: 'Xint Danmark',
rpId: 'xint.dk',
relatedOrigin: false,
enrollUrl: 'https://admin.xint.dk/#/passkey?t={token}&l={lang}',
state: 'ACTIVE'
}
Response
{ success: true, portal: { /* the stored Portal document, including _id, createdAt, updatedAt */ } }
Write-time validation
Two checks run on every create and update, against the full resulting set of portals - not
just the one being written:
Relying party reachability. A browser only accepts an rpId that is the hostname itself,
or a parent domain of it - or, failing that, the hostname must be published as a related
origin. Concretely:
* ''rpId'' with no ''.'' - a single label such as ''dk'' or ''com'' - is refused outright as a
public suffix that no browser would ever accept. ''localhost'' is the sole exception, for
development.
* Otherwise, the write is refused unless ''hostname'' (without its port) equals ''rpId'', ends
with ''.<rpId>'', or ''relatedOrigin'' is true.
- **Related-origin label budget.** If this write's ''relatedOrigin: true'' portals, combined with
every other ''ACTIVE'' related-origin portal, would use more than 5 unique registrable-domain
labels (excluding the anchor's own ''telecomx'' label), the write is refused - see
[[api:auth:webauthn]] for why 5 is the limit.
Errors
| Error code | Message | Description |
| 409 | hostname | A portal with this hostname already exists |
| 422 | rpId | rpId is a public suffix (e.g. dk), or cannot serve hostname without relatedOrigin: true |
| 422 | relatedOrigin | This write would exceed the 5-label related-origin budget |
Update
Same body and validation as create, applied to an existing portal.
Response
{ success: true, portal: { /* the updated Portal document */ } }
Errors
| Error code | Message | Description |
| 404 | portal | Portal does not exist |
| 409 | hostname | The new hostname collides with a different, existing portal |
| 422 | rpId | rpId is a public suffix, or cannot serve hostname without relatedOrigin: true |
| 422 | relatedOrigin | This write would exceed the 5-label related-origin budget |
Delete
Response
Errors
| Error code | Message | Description |
| 404 | portal | Portal does not exist |