Signing in
Signing in is sessions, passwords, single sign-on, and the roles that decide
what a caller may do. A person signs in with a password or at an identity
provider and gets a platformkit_session cookie; every request after that is
answered by the row the cookie names, in the tenant the host resolved to; and
somebody who cannot sign in is sent a link that lets them choose a password
once. The module promises that a session is a tenant’s own row, that a failed
login is recorded even though its request is rolled back, and that a reset
link is in the mail and in no row. It is also the answer every other module’s
permission is checked against. The module is
modules/auth; this page states nothing that tree
does not.
The map
features/auth in the recordWhat it promises
Entity |
|
Events |
|
Permissions |
|
Service |
|
Routes and screens
| Method | Path | Does | Authorization | Publishes |
|---|---|---|---|---|
|
|
Sign in with a password |
public |
|
|
|
Sign out |
signed in |
|
|
|
Who am I |
signed in |
— |
|
|
Change my password |
signed in |
|
|
|
Send me a reset link |
public |
|
|
|
Set a password with a link |
public |
|
|
|
List this tenant’s roles |
|
— |
|
|
Set what a role grants |
|
|
-
POST /api/v1/auth/login—Public; a wrong password and an address nobody has answer the same 401 and cost the same; ten failures for an address in fifteen minutes is a 429, and an account attacked from more than three addresses gets a two-second delay before the transaction opens instead of a lockout; a sign-in posted from another site is refused. It sets theplatformkit_sessioncookie: HttpOnly, SameSite Lax, Secure and__Host--prefixed unlessPublicHostis a local name (internal/handler.go,internal/kernel.go). -
POST /api/v1/auth/logout—SignedIn; deletes the session and clears the cookie; signing out when already signed out is not an error. -
GET /api/v1/auth/me—SignedIn; the caller’sIdentity. -
POST /api/v1/auth/password—SignedIn; requires the password in force, ends every other session of this person and keeps the one asking (internal/password.go). -
POST /api/v1/auth/password/forgot—Public; publishesauth.reset_requestedand does nothing else, so an address somebody has and one nobody has are the same answer and the same work; the 429 counts the address asking, ten per window, never the address asked about. -
POST /api/v1/auth/password/reset—Public; consumes the token the link carried, sets the password, ends every session including the caller’s and clears the cookie; an unknown, spent or expired token is one 401; twenty redemptions per address per window. -
GET /api/v1/auth/rolesandPUT /api/v1/auth/roles/{name}—role:manage; thePUTcreates the role if it is new, refuses a permission no module defines and an operator permission outside the operator’s own tenant (422), and publishesauth.role_setonly when the list changed (internal/roles.go). -
GET /api/v1/auth/oidc/startandGET /api/v1/auth/oidc/callback(the configuredredirect_path) —Public, registered only when an issuer is configured: PKCE and aplatformkit_oidcstate cookie out, a verified address back, and a session for the user this tenant has at that address; an address it has no account for is refused and nobody is created (internal/oidc.go). -
Screens: none. The module registers no
httpx.Resource, so the admin generates nothing for it; the sign-in page is the admin module’sGET /admin/login, whose form posts toPOST /api/v1/auth/login(modules/admin/internal/pages.go). The manifest’s navigation entry "Roles" names/admin/auth/rolesforrole:manage, and no page in the tree serves that path; the shell reports an unserved entry once at boot and does not show it (modules/admin/internal/mount.go). Hand-written pages: none.
Authorization
| Authorization | Who passes | Routes |
|---|---|---|
none: public |
anyone |
|
none: any signed-in member |
a session of this tenant |
|
|
a member of the tenant |
|
Events, jobs and subscriptions
| Event | Published by | Handled by |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
Publishes: the six events above;
auth.login_failedis the one event in the application written outside the transaction of the thing it describes, because a 401 is a response the kernel rolls back. -
Jobs:
auth-sweep(internal.Sweep), cron0 * * * *: purges the rate-limit windows once (limit.Purge), then throughjobs.PerTenantdeletes each tenant’s expired sessions and spent tokens a thousand rows per transaction (Purge) and logs every role that names a permission no module defines (Undeclared) (internal/sweep.go). -
Subscriptions:
user.invited→Offer, which mints a set-password token for an invited user and mails the link, skipping somebody who already has a password;auth.reset_requested→Reissue, which looks the address up in the worker and mails a link only to an active user who was not sent one in the last five minutes (module.go). Both are subscriptions rather than calls inside the request, so the request neither waits on a mail server nor reveals, by its timing, whether the address exists.
What it needs
|
Interface |
Supplied by |
|
|
the user module’s |
|
|
the notification module’s |
|
|
the same sender the notification module takes: SMTP when |
|
|
main’s |
|
|
the tenant module’s |
|
|
|
|
|
|
Configuration: auth.oidc.issuer, auth.oidc.client_id, auth.oidc.client_secret (set as PLATFORMKIT_AUTH_OIDC_CLIENT_SECRET rather than committed) and auth.oidc.redirect_path (default /api/v1/auth/oidc/callback, refused unless under /api/v1/auth/), all of it or none of it (kit/config/config.go). Session, token and lockout parameters are constants in contracts/, not configuration.
Who uses it
-
The kernel — main passes the returned
contracts.Authtokit/appasOptions.AuthorizeandOptions.Authenticate(apps/platformkit/main.go), so every permission any module declares is resolved against therolestable here, in the request’s own transaction, with no cache. -
admin —
admin.Deps.Authorizetakes the same value as anhttpx.Authorizer, to show only the links the caller may follow; its sign-in page posts toPOST /api/v1/auth/login. -
A private client module —
Deps.Authorize httpx.Authorizer, the same value, to draw a write control only where the route would let it through; the private application’s registry listsauthamong the three kernel modules no client may leave out. -
The tenant module, through main — its
OnCreatehook isseedRoles, which callsauth.SeedRoleswith the operator permissionstenant:manageandbilling:catalog; the flagship passes the client’srolesas defaults, checked withcontracts.CheckedPermissionsbefore boot;authcontracts.RoleAdminnames the first administrator’s role inbootstrapandstart. -
The admin and the native shell reach it through its routes, not through
contracts/: the shell’sloginisPOST /api/v1/auth/login, keeping theSet-Cookievalue, andlogoutisPOST /api/v1/auth/logout(platformkit-mobile,src/effects/api.ts).
Verification
-
go test ./modules/auth/…— the fake and the real service pass one conformance suite of eighteen cases (authtest.RunService: a password opens a session, a wrong password and an address nobody has answer alike, too many failures lock the address, the link sets a password once and ends every session, an operator permission cannot be granted in a customer’s tenant); the internal tests prove that a session from another tenant is not a session here, that use slides the expiry and never past the cap, that a failed login is recorded though its request is rolled back, that the reset token is in the mail and in no row, that the forgotten-password route costs the same either way, that the soft delay holds no transaction and that the lockout is one counter for every replica; the single sign-on round trip runs againstauthtest’s issuer; `seed_test.goproves role provisioning needs only its transaction. -
make e2esigns in through/admin/login, which posts toPOST /api/v1/auth/login, before every journey (e2e/admin-tasks.spec.ts); a private shopper journey signs in the same way. -
Not proven: single sign-on against a live identity provider; the password flows in a browser, since no journey follows a mailed link; the sweep against a live fleet.