UNPKG

@adonis-agora/authkit-react

Version:

Frontend ergonomics over AuthKit for AdonisJS + Inertia + React apps: a typed useAuth() hook, role-gating hooks and gating components.

225 lines (224 loc) 9.9 kB
export class AuthkitClientError extends Error { status; code; body; constructor(status, message, code, body) { super(message); this.status = status; this.code = code; this.body = body; this.name = 'AuthkitClientError'; Object.defineProperty(this, 'isUnauthorized', { value: status === 401, enumerable: true }); } isUnauthorized; } const MUTATING = new Set(['POST', 'PUT', 'PATCH', 'DELETE']); function resolveWindow() { try { if (typeof window === 'undefined') return null; return window.__AUTHKIT__ ?? null; } catch { return null; } } function toQueryString(params) { const parts = []; for (const [k, v] of Object.entries(params)) { if (v === undefined || v === null) continue; parts.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`); } return parts.length ? `?${parts.join('&')}` : ''; } class AuthkitClient { _baseUrl; _accountBaseUrl; _csrfToken; _fetch; constructor(opts = {}) { const win = resolveWindow(); this._baseUrl = opts.baseUrl ?? win?.endpoints.api; this._accountBaseUrl = opts.accountBaseUrl ?? '/account/api'; this._csrfToken = opts.csrfToken ?? win?.csrfToken; this._fetch = opts.fetch ?? globalThis.fetch.bind(globalThis); } async request(method, url, body, extraHeaders) { const isMutating = MUTATING.has(method.toUpperCase()); const headers = { Accept: 'application/json', ...extraHeaders, }; if (isMutating && this._csrfToken) { headers['X-CSRF-TOKEN'] = this._csrfToken; } if (body !== undefined) { headers['Content-Type'] = 'application/json'; } const res = await this._fetch(url, { method: method.toUpperCase(), credentials: 'include', headers, ...(body !== undefined ? { body: JSON.stringify(body) } : {}), }); if (!res.ok) { let message = `Request failed (${res.status})`; let code; let parsed; try { parsed = await res.json(); const envelope = parsed; if (envelope?.error?.message) message = envelope.error.message; if (envelope?.error?.code) code = envelope.error.code; else if (typeof envelope?.message === 'string') message = envelope.message; } catch { } throw new AuthkitClientError(res.status, message, code, parsed); } const text = await res.text(); return text ? JSON.parse(text) : null; } get(path, params) { const qs = params ? toQueryString(params) : ''; return this.request('GET', `${path}${qs}`); } post(path, body) { return this.request('POST', path, body); } patch(path, body) { return this.request('PATCH', path, body); } put(path, body) { return this.request('PUT', path, body); } delete(path) { return this.request('DELETE', path); } b(path) { if (!this._baseUrl) { throw new Error('[AuthkitClient] admin base URL ausente: passe `baseUrl` em createAuthkitClient({ baseUrl }) ' + 'ou use o client onde window.__AUTHKIT__ está injetado (console admin). ' + 'Telas que só usam client.account.* não precisam de baseUrl.'); } return `${this._baseUrl}${path}`; } a(path) { return `${this._accountBaseUrl}${path}`; } admin = { overview: () => this.get(this.b('/overview')), users: { list: (params) => this.get(this.b('/users'), params), get: (id) => this.get(this.b(`/users/${encodeURIComponent(id)}`)), create: (data) => this.post(this.b('/users'), data), update: (id, data) => this.patch(this.b(`/users/${encodeURIComponent(id)}`), data), disable: (id) => this.post(this.b(`/users/${encodeURIComponent(id)}/disable`)), enable: (id) => this.post(this.b(`/users/${encodeURIComponent(id)}/enable`)), resetPassword: (id) => this.post(this.b(`/users/${encodeURIComponent(id)}/reset-password`)), remove: (id) => this.delete(this.b(`/users/${encodeURIComponent(id)}`)), getSessions: (id) => this.get(this.b(`/users/${encodeURIComponent(id)}/sessions`)), revokeSessions: (id) => this.post(this.b(`/users/${encodeURIComponent(id)}/revoke-sessions`)), }, sessions: { list: (accountId) => this.get(this.b('/sessions'), accountId ? { accountId } : undefined), revokeAll: (accountId) => this.post(this.b('/sessions/revoke-all'), accountId ? { accountId } : undefined), }, clients: { list: () => this.get(this.b('/clients')), get: (id) => this.get(this.b(`/clients/${encodeURIComponent(id)}`)), create: (data) => this.post(this.b('/clients'), data), update: (id, data) => this.patch(this.b(`/clients/${encodeURIComponent(id)}`), data), remove: (id) => this.delete(this.b(`/clients/${encodeURIComponent(id)}`)), regenerateSecret: (id) => this.post(this.b(`/clients/${encodeURIComponent(id)}/regenerate-secret`)), }, roles: { list: () => this.get(this.b('/roles')), create: (data) => this.post(this.b('/roles'), data), update: (name, data) => this.patch(this.b(`/roles/${encodeURIComponent(name)}`), data), remove: (name) => this.delete(this.b(`/roles/${encodeURIComponent(name)}`)), }, orgs: { list: () => this.get(this.b('/orgs')), create: (data) => this.post(this.b('/orgs'), data), get: (id) => this.get(this.b(`/orgs/${encodeURIComponent(id)}`)), update: (id, data) => this.patch(this.b(`/orgs/${encodeURIComponent(id)}`), data), remove: (id) => this.delete(this.b(`/orgs/${encodeURIComponent(id)}`)), addMember: (orgId, data) => this.post(this.b(`/orgs/${encodeURIComponent(orgId)}/members`), data), removeMember: (orgId, accountId) => this.delete(this.b(`/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(accountId)}`)), updateMemberRole: (orgId, accountId, role) => this.patch(this.b(`/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(accountId)}`), { role }), createInvitation: (orgId, data) => this.post(this.b(`/orgs/${encodeURIComponent(orgId)}/invitations`), data), revokeInvitation: (orgId, invitationId) => this.delete(this.b(`/orgs/${encodeURIComponent(orgId)}/invitations/${encodeURIComponent(invitationId)}`)), }, audit: { list: (params) => this.get(this.b('/audit'), params), }, settings: { list: (orgId) => { const qs = orgId ? `?organizationId=${encodeURIComponent(orgId)}` : ''; return this.get(this.b(`/settings${qs}`)); }, set: (key, value, orgId) => { const qs = orgId ? `?organizationId=${encodeURIComponent(orgId)}` : ''; return this.put(this.b(`/settings/${encodeURIComponent(key)}${qs}`), { value, }); }, remove: (key, orgId) => { const qs = orgId ? `?organizationId=${encodeURIComponent(orgId)}` : ''; return this.delete(this.b(`/settings/${encodeURIComponent(key)}${qs}`)); }, }, impersonation: { get: (userId) => this.get(this.b(`/impersonation/${encodeURIComponent(userId)}`)), }, keys: { status: () => this.get(this.b('/keys')), rotate: (input) => this.post(this.b('/keys/rotate'), input ?? {}), }, }; account = { me: () => this.get(this.a('/me')), security: () => this.get(this.a('/security')), updateProfile: (data) => this.patch(this.a('/profile'), data), changePassword: (data) => this.post(this.a('/password'), data), emailChange: (data) => this.post(this.a('/email-change'), data), cancelEmailChange: () => this.post(this.a('/email-change/cancel')), sessions: { list: () => this.get(this.a('/sessions')), revoke: (id) => this.delete(this.a(`/sessions/${encodeURIComponent(id)}`)), revokeOthers: () => this.post(this.a('/sessions/revoke-others')), revokeAll: () => this.post(this.a('/sessions/revoke-all')), }, apps: { list: () => this.get(this.a('/apps')), revoke: (clientId) => this.delete(this.a(`/apps/${encodeURIComponent(clientId)}`)), }, mfa: () => this.get(this.a('/mfa')), loginMethods: { get: () => this.get(this.a('/login-methods')), update: (data) => this.put(this.a('/login-methods'), data), }, passkeys: { list: () => this.get(this.a('/passkeys')), remove: (id) => this.delete(this.a(`/passkeys/${encodeURIComponent(id)}`)), }, tokens: { list: () => this.get(this.a('/tokens')), create: (data) => this.post(this.a('/tokens'), data), remove: (id) => this.delete(this.a(`/tokens/${encodeURIComponent(id)}`)), }, orgs: { list: () => this.get(this.a('/orgs')), invitations: () => this.get(this.a('/orgs/invitations')), get: (id) => this.get(this.a(`/orgs/${encodeURIComponent(id)}`)), }, }; } export function createAuthkitClient(opts) { return new AuthkitClient(opts); }