@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
JavaScript
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);
}