Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / frontend/src/lib/auth.ts |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | frontend/src/lib/auth.ts |
| Original SHA-256 | 99a615364eb15eb1914c26030fd764fdf762b7c788db1c23157d31272904e368 |
| Git blob SHA | 590ef74353a17f6473e48f6eeb103a61560dba6e |
| Reading-copy SHA-256 | a45f8bbb0e889e148976abb3fca472d2e4ea152c4a8883c96031030c185cb975 |
Recovered source transcript
Download complete source text ↓101 lines · 4.6 KB
Credential-bearing and unrelated personal details were removed from this reading copy. The source identity and original checksum remain recorded.
import { request } from './api'
// The validated session principal returned by GET /api/auth/session (a tenant route, so it 403s when
// there is no active MFA satisfied session).
export interface SessionPrincipal {
user_id: string
org_id: string
role: string
mfa_enrolled: boolean
// The resolved permission list, computed server side from the same matrix the API enforces. Gating on
// this rather than on role name comparisons keeps one source of truth: duplicating the matrix in
// TypeScript is how a screen ends up offering something the API refuses.
//
// It decides what is OFFERED, never what is allowed. Every endpoint keeps its own gate.
capabilities: string[]
// The granted product set. Role says what the user may DO; this says which of the four tools exists
// for them at all. Like capabilities it decides what is OFFERED: each tool's API route group carries
// its own server side gate, so hiding a tool here is presentation, not the enforcement.
//
// This is the EFFECTIVE set, already widened server side for the top seats, so a tenant superuser
// provisioned with no grant rows still sees its tenant's products.
tools: string[]
// What the ORGANIZATION is entitled to, which is not the same question as what this user holds. Both
// are needed to say WHY an entitlement control is disabled instead of leaving it silently grey.
org_tools?: string[]
// The signed in person's own identity, so the profile form opens with current values and a rename
// renders everywhere on the next session read rather than only after a sign out.
full_name?: string
title?: string
phone?: string
}
// ProfileInput sends only what is being changed. An omitted field is left alone by the server rather
// than blanked, which is why every field is optional rather than defaulting to an empty string.
export interface ProfileInput {
full_name?: string
title?: string
phone?: string
}
// Profile is the server's view of an identity after a write.
export interface Profile {
id: string
email: string
full_name: string
title: string
phone: string
role: string
status: string
}
// LoginResult and the invite accept result tell the UI the next step: verify an existing TOTP, or enroll
// one for the first time. The session token itself is the HttpOnly cookie, never in the JSON. When
// mfa_required is false (a pilot deployment with MFA disabled) the issued session is already active, so
// the UI skips the MFA step entirely.
export interface AuthStepResult {
mfa_enrolled: boolean
mfa_required: boolean
}
// EnrollResult carries the otpauth uri (rendered as a QR) and the raw secret (for manual entry).
export interface EnrollResult {
otpauth_uri: string
secret: string
}
export const authApi = {
login: (email: string, password: [credential removed] =>
request<AuthStepResult>('/auth/login', { method: 'POST', body: JSON.stringify({ email, password }) }),
inviteAccept: (token: string, password: [credential removed] =>
request<AuthStepResult>('/auth/invite/accept', { method: 'POST', body: JSON.stringify({ token, password }) }),
mfaEnroll: () => request<EnrollResult>('/auth/mfa/enroll', { method: 'POST' }),
mfaActivate: (code: string) =>
request<AuthStepResult>('/auth/mfa/activate', { method: 'POST', body: JSON.stringify({ code }) }),
mfaVerify: (code: string) =>
request<{ ok: boolean }>('/auth/mfa/verify', { method: 'POST', body: JSON.stringify({ code }) }),
mfaRecover: (code: string) =>
request<{ ok: boolean }>('/auth/mfa/recover', { method: 'POST', body: JSON.stringify({ code }) }),
session: () => request<SessionPrincipal>('/auth/session'),
logout: () => request<{ status: string }>('/auth/logout', { method: 'POST' }),
logoutAll: () => request<{ status: string }>('/auth/logout-all', { method: 'POST' }),
changePassword: [credential removed] string, new_password: [credential removed] =>
request<{ status: string }>('/auth/password/change', {
method: 'POST',
body: JSON.stringify({ old_password, new_password }),
}),
// Self service, every role. The server takes the subject from the session, so there is no user id to
// send and no way to point this at somebody else.
updateOwnProfile: (v: ProfileInput) =>
request<Profile>('/me/profile', { method: 'PATCH', body: JSON.stringify(v) }),
// Administrative, admin and above, bounded by outranking at the API. Used by the accounts screen so a
// client admin can correct a colleague's name without anyone escalating.
updateUserProfile: (id: string, v: ProfileInput) =>
request<Profile>(`/users/${id}/profile`, { method: 'PATCH', body: JSON.stringify(v) }),
}