feat: partner enrichment, mutations, settings & branding + operator quick-wins
Backend (platform-api): computed tenant health plus industry/brandColor; partner-scoped tenant update/suspend/resume guarded by assertPartnerOwnsTenant; enriched partner users (MFA + access level) with invite/remove; partner settings and whitelabel branding persistence; Authentik authenticator counting and group removal. Audit on every mutation. Frontend (portal): all five partner pages on real data — dashboard alerts, customers edit/suspend, team MFA/access with invite/remove, editable settings, branding fetch/save. Operator: dashboard and infrastructure service health driven by real liveness probes; fabricated uptime/p95/error-rate removed.
This commit is contained in:
@@ -141,6 +141,40 @@ export class AuthentikClient {
|
||||
this.logger.log(`Added user ${userPk} to Authentik group ${groupId}`)
|
||||
}
|
||||
|
||||
// Remove a user from a group by ID. Authentik 204s even if the user wasn't
|
||||
// a member, so this is effectively idempotent.
|
||||
async removeUserFromGroup(userPk: number, groupId: string): Promise<void> {
|
||||
await this.request(`/core/groups/${groupId}/remove_user/`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ pk: userPk }),
|
||||
})
|
||||
this.logger.log(`Removed user ${userPk} from Authentik group ${groupId}`)
|
||||
}
|
||||
|
||||
// Count a user's configured authenticators (TOTP / WebAuthn / static). Used
|
||||
// to surface an "MFA enrolled" badge on the partner team list — callers treat
|
||||
// a count > 0 as enrolled. Authentik has no single "all devices" admin route;
|
||||
// it exposes one per device type, so we query the common three and sum. Each
|
||||
// returns a paginated { results } envelope.
|
||||
async countAuthenticators(userPk: number): Promise<number> {
|
||||
const types = ['totp', 'webauthn', 'static']
|
||||
const counts = await Promise.all(
|
||||
types.map(async (t) => {
|
||||
try {
|
||||
const res = await this.request<{ results?: unknown[] }>(
|
||||
`/authenticators/admin/${t}/?user=${userPk}`,
|
||||
)
|
||||
return Array.isArray(res?.results) ? res.results.length : 0
|
||||
} catch {
|
||||
// A device type not enabled on this Authentik instance returns 404 —
|
||||
// don't let it zero out the types that do resolve.
|
||||
return 0
|
||||
}
|
||||
}),
|
||||
)
|
||||
return counts.reduce((a, b) => a + b, 0)
|
||||
}
|
||||
|
||||
// Generate a single-use recovery link the new user clicks to set their
|
||||
// password + enroll MFA. Requires a "recovery flow" configured on the
|
||||
// Authentik brand — if not set, returns undefined so callers can fall
|
||||
|
||||
Reference in New Issue
Block a user