@dudousxd/adonis-authkit-react 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/build/index.d.ts +47 -0
- package/build/index.js +24 -0
- package/build/src/authkit_provider.d.ts +13 -0
- package/build/src/authkit_provider.js +11 -0
- package/build/src/components/authorized_apps.d.ts +8 -0
- package/build/src/components/authorized_apps.js +26 -0
- package/build/src/components/avatar.d.ts +22 -0
- package/build/src/components/avatar.js +15 -0
- package/build/src/components/organization_profile.d.ts +8 -0
- package/build/src/components/organization_profile.js +87 -0
- package/build/src/components/organization_switcher.d.ts +7 -0
- package/build/src/components/organization_switcher.js +57 -0
- package/build/src/components/password_strength_meter.d.ts +11 -0
- package/build/src/components/password_strength_meter.js +39 -0
- package/build/src/components/sign_in_button.d.ts +297 -0
- package/build/src/components/sign_in_button.js +16 -0
- package/build/src/components/sign_out_button.d.ts +296 -0
- package/build/src/components/sign_out_button.js +16 -0
- package/build/src/components/user_button.d.ts +8 -0
- package/build/src/components/user_button.js +31 -0
- package/build/src/components/user_profile.d.ts +6 -0
- package/build/src/components/user_profile.js +27 -0
- package/build/src/config.d.ts +27 -0
- package/build/src/config.js +40 -0
- package/build/src/hooks/use_authorized_apps.d.ts +16 -0
- package/build/src/hooks/use_authorized_apps.js +15 -0
- package/build/src/hooks/use_org_invitations.d.ts +19 -0
- package/build/src/hooks/use_org_invitations.js +20 -0
- package/build/src/hooks/use_organization.d.ts +23 -0
- package/build/src/hooks/use_organization.js +8 -0
- package/build/src/hooks/use_organizations.d.ts +18 -0
- package/build/src/hooks/use_organizations.js +14 -0
- package/build/src/hooks/use_password_strength.d.ts +11 -0
- package/build/src/hooks/use_password_strength.js +34 -0
- package/build/src/hooks/use_profile.d.ts +13 -0
- package/build/src/hooks/use_profile.js +28 -0
- package/build/src/hooks/use_resource.d.ts +11 -0
- package/build/src/hooks/use_resource.js +48 -0
- package/build/src/hooks/use_sessions.d.ts +16 -0
- package/build/src/hooks/use_sessions.js +15 -0
- package/build/src/hooks/use_sign_in.d.ts +6 -0
- package/build/src/hooks/use_sign_in.js +13 -0
- package/build/src/hooks/use_sign_out.d.ts +6 -0
- package/build/src/hooks/use_sign_out.js +11 -0
- package/build/src/hooks/use_switch_organization.d.ts +7 -0
- package/build/src/hooks/use_switch_organization.js +42 -0
- package/build/src/hooks/use_user.d.ts +6 -0
- package/build/src/hooks/use_user.js +5 -0
- package/build/src/use_auth.js +1 -1
- package/build/src/utils.d.ts +2 -0
- package/build/src/utils.js +18 -0
- package/package.json +9 -7
- package/styles.css +294 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createElement } from 'react';
|
|
2
|
+
import { useSignOut } from '../hooks/use_sign_out.js';
|
|
3
|
+
import { useAuth } from '../use_auth.js';
|
|
4
|
+
export function SignOutButton({ children = 'Sair', returnTo, className, ...rest }) {
|
|
5
|
+
const { signOut } = useSignOut();
|
|
6
|
+
const { isAuthenticated } = useAuth();
|
|
7
|
+
if (!isAuthenticated)
|
|
8
|
+
return null;
|
|
9
|
+
const opts = returnTo ? { returnTo } : undefined;
|
|
10
|
+
return createElement('button', {
|
|
11
|
+
type: 'button',
|
|
12
|
+
className: ['authkit-button', 'authkit-button--ghost', className].filter(Boolean).join(' '),
|
|
13
|
+
onClick: () => signOut(opts),
|
|
14
|
+
...rest,
|
|
15
|
+
}, children);
|
|
16
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface UserButtonProps {
|
|
2
|
+
profileLabel?: string;
|
|
3
|
+
signOutLabel?: string;
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function UserButton({ profileLabel, signOutLabel, className, }: UserButtonProps): import("react").DetailedReactHTMLElement<{
|
|
7
|
+
className: string;
|
|
8
|
+
}, HTMLElement> | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createElement, useState } from 'react';
|
|
2
|
+
import { useAuth } from '../use_auth.js';
|
|
3
|
+
import { useAuthkitConfig } from '../config.js';
|
|
4
|
+
import { useSignOut } from '../hooks/use_sign_out.js';
|
|
5
|
+
import { Avatar } from './avatar.js';
|
|
6
|
+
export function UserButton({ profileLabel = 'Perfil', signOutLabel = 'Sair', className, }) {
|
|
7
|
+
const { user, isAuthenticated } = useAuth();
|
|
8
|
+
const config = useAuthkitConfig();
|
|
9
|
+
const { signOut } = useSignOut();
|
|
10
|
+
const [open, setOpen] = useState(false);
|
|
11
|
+
if (!isAuthenticated || !user)
|
|
12
|
+
return null;
|
|
13
|
+
const trigger = createElement('button', {
|
|
14
|
+
type: 'button',
|
|
15
|
+
className: 'authkit-userbutton__trigger',
|
|
16
|
+
'aria-haspopup': 'menu',
|
|
17
|
+
'aria-expanded': open,
|
|
18
|
+
onClick: () => setOpen((v) => !v),
|
|
19
|
+
}, createElement(Avatar, { user }));
|
|
20
|
+
const menu = open
|
|
21
|
+
? createElement('div', { className: 'authkit-userbutton__menu', role: 'menu' }, createElement('div', { className: 'authkit-userbutton__header' }, createElement('div', { className: 'authkit-userbutton__name' }, user.name ?? user.email), user.name
|
|
22
|
+
? createElement('div', { className: 'authkit-userbutton__email' }, user.email)
|
|
23
|
+
: null), createElement('a', { className: 'authkit-userbutton__item', role: 'menuitem', href: config.profileUrl }, profileLabel), createElement('button', {
|
|
24
|
+
type: 'button',
|
|
25
|
+
className: 'authkit-userbutton__item',
|
|
26
|
+
role: 'menuitem',
|
|
27
|
+
onClick: () => signOut(),
|
|
28
|
+
}, signOutLabel))
|
|
29
|
+
: null;
|
|
30
|
+
return createElement('div', { className: ['authkit-userbutton', className].filter(Boolean).join(' ') }, trigger, menu);
|
|
31
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createElement, useState } from 'react';
|
|
2
|
+
import { useAuth } from '../use_auth.js';
|
|
3
|
+
import { useProfile } from '../hooks/use_profile.js';
|
|
4
|
+
import { Avatar } from './avatar.js';
|
|
5
|
+
export function UserProfile({ className }) {
|
|
6
|
+
const { user, isAuthenticated } = useAuth();
|
|
7
|
+
const { actions, loading, error } = useProfile();
|
|
8
|
+
const [name, setName] = useState(user?.name ?? '');
|
|
9
|
+
if (!isAuthenticated || !user)
|
|
10
|
+
return null;
|
|
11
|
+
const onSubmit = (e) => {
|
|
12
|
+
e.preventDefault();
|
|
13
|
+
void actions.update({ name });
|
|
14
|
+
};
|
|
15
|
+
return createElement('div', { className: ['authkit-card', 'authkit-profile', className].filter(Boolean).join(' ') }, createElement('div', { className: 'authkit-profile__header' }, createElement(Avatar, { user, size: 56 }), createElement('div', null, createElement('div', { className: 'authkit-profile__name' }, user.name ?? user.email), createElement('div', { className: 'authkit-profile__email' }, user.email))), createElement('form', { className: 'authkit-profile__form', onSubmit }, createElement('label', { className: 'authkit-label', htmlFor: 'authkit-profile-name' }, 'Nome'), createElement('input', {
|
|
16
|
+
id: 'authkit-profile-name',
|
|
17
|
+
className: 'authkit-input',
|
|
18
|
+
value: name,
|
|
19
|
+
onChange: (e) => setName(e.target.value),
|
|
20
|
+
}), error
|
|
21
|
+
? createElement('p', { className: 'authkit-error', role: 'alert' }, error.message)
|
|
22
|
+
: null, createElement('button', {
|
|
23
|
+
type: 'submit',
|
|
24
|
+
className: 'authkit-button authkit-button--primary',
|
|
25
|
+
disabled: loading,
|
|
26
|
+
}, loading ? 'Salvando…' : 'Salvar')));
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface AuthkitEndpoints {
|
|
2
|
+
profile: string;
|
|
3
|
+
sessions: string;
|
|
4
|
+
apps: string;
|
|
5
|
+
passkeys: string;
|
|
6
|
+
orgs: string;
|
|
7
|
+
orgInvitations: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AuthkitConfig {
|
|
10
|
+
loginUrl?: string;
|
|
11
|
+
logoutUrl?: string;
|
|
12
|
+
profileUrl?: string;
|
|
13
|
+
endpoints?: Partial<AuthkitEndpoints>;
|
|
14
|
+
csrfToken?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ResolvedAuthkitConfig {
|
|
17
|
+
loginUrl: string;
|
|
18
|
+
logoutUrl: string;
|
|
19
|
+
profileUrl: string;
|
|
20
|
+
endpoints: AuthkitEndpoints;
|
|
21
|
+
csrfToken?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const DEFAULT_CONFIG: ResolvedAuthkitConfig;
|
|
24
|
+
export declare function resolveConfig(config?: AuthkitConfig): ResolvedAuthkitConfig;
|
|
25
|
+
export declare function buildAuthUrl(base: string, returnTo?: string): string;
|
|
26
|
+
export declare const AuthkitConfigContext: import("react").Context<ResolvedAuthkitConfig>;
|
|
27
|
+
export declare function useAuthkitConfig(): ResolvedAuthkitConfig;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
export const DEFAULT_CONFIG = {
|
|
3
|
+
loginUrl: '/auth/login',
|
|
4
|
+
logoutUrl: '/account/logout',
|
|
5
|
+
profileUrl: '/account/security',
|
|
6
|
+
endpoints: {
|
|
7
|
+
profile: '/account/security/profile',
|
|
8
|
+
sessions: '/account/security',
|
|
9
|
+
apps: '/account/apps',
|
|
10
|
+
passkeys: '/account/mfa/passkeys',
|
|
11
|
+
orgs: '/account/orgs/json',
|
|
12
|
+
orgInvitations: '/account/orgs/invitations/json',
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export function resolveConfig(config) {
|
|
16
|
+
return {
|
|
17
|
+
loginUrl: config?.loginUrl ?? DEFAULT_CONFIG.loginUrl,
|
|
18
|
+
logoutUrl: config?.logoutUrl ?? DEFAULT_CONFIG.logoutUrl,
|
|
19
|
+
profileUrl: config?.profileUrl ?? DEFAULT_CONFIG.profileUrl,
|
|
20
|
+
endpoints: {
|
|
21
|
+
profile: config?.endpoints?.profile ?? DEFAULT_CONFIG.endpoints.profile,
|
|
22
|
+
sessions: config?.endpoints?.sessions ?? DEFAULT_CONFIG.endpoints.sessions,
|
|
23
|
+
apps: config?.endpoints?.apps ?? DEFAULT_CONFIG.endpoints.apps,
|
|
24
|
+
passkeys: config?.endpoints?.passkeys ?? DEFAULT_CONFIG.endpoints.passkeys,
|
|
25
|
+
orgs: config?.endpoints?.orgs ?? DEFAULT_CONFIG.endpoints.orgs,
|
|
26
|
+
orgInvitations: config?.endpoints?.orgInvitations ?? DEFAULT_CONFIG.endpoints.orgInvitations,
|
|
27
|
+
},
|
|
28
|
+
csrfToken: config?.csrfToken,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function buildAuthUrl(base, returnTo) {
|
|
32
|
+
if (!returnTo)
|
|
33
|
+
return base;
|
|
34
|
+
const sep = base.includes('?') ? '&' : '?';
|
|
35
|
+
return `${base}${sep}returnTo=${encodeURIComponent(returnTo)}`;
|
|
36
|
+
}
|
|
37
|
+
export const AuthkitConfigContext = createContext(DEFAULT_CONFIG);
|
|
38
|
+
export function useAuthkitConfig() {
|
|
39
|
+
return useContext(AuthkitConfigContext);
|
|
40
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ResourceState } from './use_resource.js';
|
|
2
|
+
export interface AuthorizedApp {
|
|
3
|
+
clientId: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
logoUrl?: string;
|
|
6
|
+
scopes?: string[];
|
|
7
|
+
authorizedAt?: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface UseAuthorizedAppsResult extends ResourceState<AuthorizedApp[]> {
|
|
11
|
+
actions: {
|
|
12
|
+
refetch(): Promise<void>;
|
|
13
|
+
revoke(clientId: string): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare function useAuthorizedApps(): UseAuthorizedAppsResult;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useAuthkitConfig } from '../config.js';
|
|
3
|
+
import { jsonRequest, useResource } from './use_resource.js';
|
|
4
|
+
export function useAuthorizedApps() {
|
|
5
|
+
const config = useAuthkitConfig();
|
|
6
|
+
const { data, loading, error, refetch } = useResource(config.endpoints.apps, config.csrfToken);
|
|
7
|
+
const revoke = useCallback(async (clientId) => {
|
|
8
|
+
await jsonRequest(`${config.endpoints.apps}/${encodeURIComponent(clientId)}/revoke`, {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
csrfToken: config.csrfToken,
|
|
11
|
+
});
|
|
12
|
+
await refetch();
|
|
13
|
+
}, [config.endpoints.apps, config.csrfToken, refetch]);
|
|
14
|
+
return { data, loading, error, actions: { refetch, revoke } };
|
|
15
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ResourceState } from './use_resource.js';
|
|
2
|
+
export interface OrgInvitationEntry {
|
|
3
|
+
id: string;
|
|
4
|
+
organizationId: string;
|
|
5
|
+
orgName: string;
|
|
6
|
+
orgSlug: string;
|
|
7
|
+
email: string;
|
|
8
|
+
role: string;
|
|
9
|
+
expiresAt: string;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface UseOrgInvitationsResult extends ResourceState<OrgInvitationEntry[]> {
|
|
14
|
+
actions: {
|
|
15
|
+
refetch(): Promise<void>;
|
|
16
|
+
accept(token: string): Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export declare function useOrgInvitations(): UseOrgInvitationsResult;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useAuthkitConfig } from '../config.js';
|
|
3
|
+
import { jsonRequest, useResource } from './use_resource.js';
|
|
4
|
+
export function useOrgInvitations() {
|
|
5
|
+
const config = useAuthkitConfig();
|
|
6
|
+
const { data, loading, error, refetch } = useResource(config.endpoints.orgInvitations, config.csrfToken);
|
|
7
|
+
const accept = useCallback(async (token) => {
|
|
8
|
+
await jsonRequest(`/account/orgs/invitations/${encodeURIComponent(token)}/accept`, {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
csrfToken: config.csrfToken,
|
|
11
|
+
});
|
|
12
|
+
await refetch();
|
|
13
|
+
}, [config.csrfToken, refetch]);
|
|
14
|
+
return {
|
|
15
|
+
data: data?.invitations ?? null,
|
|
16
|
+
loading,
|
|
17
|
+
error,
|
|
18
|
+
actions: { refetch, accept },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ResourceState } from './use_resource.js';
|
|
2
|
+
export interface OrgMemberEntry {
|
|
3
|
+
accountId: string;
|
|
4
|
+
email: string | null;
|
|
5
|
+
role: string;
|
|
6
|
+
joinedAt: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ActiveOrgDetail {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
slug: string;
|
|
12
|
+
logoUrl: string | null;
|
|
13
|
+
role: string;
|
|
14
|
+
canManage: boolean;
|
|
15
|
+
members: OrgMemberEntry[];
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface UseOrganizationResult extends ResourceState<ActiveOrgDetail> {
|
|
19
|
+
actions: {
|
|
20
|
+
refetch(): Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export declare function useOrganization(orgId: string | null): UseOrganizationResult;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { useAuthkitConfig } from '../config.js';
|
|
2
|
+
import { useResource } from './use_resource.js';
|
|
3
|
+
export function useOrganization(orgId) {
|
|
4
|
+
const config = useAuthkitConfig();
|
|
5
|
+
const url = orgId ? `${config.endpoints.orgs.replace('/json', '')}/${encodeURIComponent(orgId)}/json` : '';
|
|
6
|
+
const { data, loading, error, refetch } = useResource(url, config.csrfToken);
|
|
7
|
+
return { data, loading: url ? loading : false, error, actions: { refetch } };
|
|
8
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ResourceState } from './use_resource.js';
|
|
2
|
+
export interface OrgEntry {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
slug: string;
|
|
6
|
+
logoUrl: string | null;
|
|
7
|
+
role: string;
|
|
8
|
+
isActive: boolean;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface UseOrganizationsResult extends ResourceState<OrgEntry[]> {
|
|
12
|
+
activeOrgId: string | null;
|
|
13
|
+
supported: boolean;
|
|
14
|
+
actions: {
|
|
15
|
+
refetch(): Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export declare function useOrganizations(): UseOrganizationsResult;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useAuthkitConfig } from '../config.js';
|
|
2
|
+
import { useResource } from './use_resource.js';
|
|
3
|
+
export function useOrganizations() {
|
|
4
|
+
const config = useAuthkitConfig();
|
|
5
|
+
const { data, loading, error, refetch } = useResource(config.endpoints.orgs, config.csrfToken);
|
|
6
|
+
return {
|
|
7
|
+
data: data?.orgs ?? null,
|
|
8
|
+
loading,
|
|
9
|
+
error,
|
|
10
|
+
activeOrgId: data?.activeOrgId ?? null,
|
|
11
|
+
supported: data?.supported ?? true,
|
|
12
|
+
actions: { refetch },
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type PasswordStrengthScore = 0 | 1 | 2 | 3 | 4;
|
|
2
|
+
export interface PasswordStrengthResult {
|
|
3
|
+
score: PasswordStrengthScore;
|
|
4
|
+
feedback?: string[];
|
|
5
|
+
}
|
|
6
|
+
export type PasswordScorer = (password: string) => PasswordStrengthResult;
|
|
7
|
+
export interface UsePasswordStrengthOptions {
|
|
8
|
+
scorer?: PasswordScorer;
|
|
9
|
+
}
|
|
10
|
+
export declare function heuristicScorer(password: string): PasswordStrengthResult;
|
|
11
|
+
export declare function usePasswordStrength(password: string, options?: UsePasswordStrengthOptions): PasswordStrengthResult;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
export function heuristicScorer(password) {
|
|
3
|
+
if (!password)
|
|
4
|
+
return { score: 0, feedback: ['Enter a password.'] };
|
|
5
|
+
const feedback = [];
|
|
6
|
+
const classes = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/];
|
|
7
|
+
const variety = classes.filter((re) => re.test(password)).length;
|
|
8
|
+
const length = password.length;
|
|
9
|
+
let points = 0;
|
|
10
|
+
if (length >= 8)
|
|
11
|
+
points += 1;
|
|
12
|
+
if (length >= 12)
|
|
13
|
+
points += 1;
|
|
14
|
+
if (length >= 16)
|
|
15
|
+
points += 1;
|
|
16
|
+
if (variety >= 2)
|
|
17
|
+
points += 1;
|
|
18
|
+
if (variety >= 3)
|
|
19
|
+
points += 1;
|
|
20
|
+
if (length < 12)
|
|
21
|
+
feedback.push('Use at least 12 characters.');
|
|
22
|
+
if (!/[A-Z]/.test(password))
|
|
23
|
+
feedback.push('Add an uppercase letter.');
|
|
24
|
+
if (!/[0-9]/.test(password))
|
|
25
|
+
feedback.push('Add a number.');
|
|
26
|
+
if (!/[^A-Za-z0-9]/.test(password))
|
|
27
|
+
feedback.push('Add a symbol.');
|
|
28
|
+
const score = Math.max(0, Math.min(4, points));
|
|
29
|
+
return { score, feedback: feedback.length ? feedback : undefined };
|
|
30
|
+
}
|
|
31
|
+
export function usePasswordStrength(password, options = {}) {
|
|
32
|
+
const scorer = options.scorer ?? heuristicScorer;
|
|
33
|
+
return useMemo(() => scorer(password), [password, scorer]);
|
|
34
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ResourceState } from './use_resource.js';
|
|
2
|
+
import type { AuthUser } from '../types.js';
|
|
3
|
+
export interface ProfileUpdate {
|
|
4
|
+
name?: string;
|
|
5
|
+
avatarUrl?: string;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface UseProfileResult extends ResourceState<AuthUser> {
|
|
9
|
+
actions: {
|
|
10
|
+
update(data: ProfileUpdate): Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export declare function useProfile(): UseProfileResult;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import { useAuthkitConfig } from '../config.js';
|
|
3
|
+
import { useAuth } from '../use_auth.js';
|
|
4
|
+
import { jsonRequest } from './use_resource.js';
|
|
5
|
+
export function useProfile() {
|
|
6
|
+
const config = useAuthkitConfig();
|
|
7
|
+
const { user } = useAuth();
|
|
8
|
+
const [state, setState] = useState({
|
|
9
|
+
data: user,
|
|
10
|
+
loading: false,
|
|
11
|
+
error: null,
|
|
12
|
+
});
|
|
13
|
+
const update = useCallback(async (data) => {
|
|
14
|
+
setState((s) => ({ ...s, loading: true, error: null }));
|
|
15
|
+
try {
|
|
16
|
+
const updated = await jsonRequest(config.endpoints.profile, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
body: JSON.stringify(data),
|
|
19
|
+
csrfToken: config.csrfToken,
|
|
20
|
+
});
|
|
21
|
+
setState({ data: updated ?? { ...user, ...data }, loading: false, error: null });
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
setState((s) => ({ ...s, loading: false, error: err }));
|
|
25
|
+
}
|
|
26
|
+
}, [config.endpoints.profile, config.csrfToken, user]);
|
|
27
|
+
return { ...state, actions: { update } };
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ResourceState<T> {
|
|
2
|
+
data: T | null;
|
|
3
|
+
loading: boolean;
|
|
4
|
+
error: Error | null;
|
|
5
|
+
}
|
|
6
|
+
export declare function jsonRequest<T>(url: string, init?: RequestInit & {
|
|
7
|
+
csrfToken?: string;
|
|
8
|
+
}): Promise<T>;
|
|
9
|
+
export declare function useResource<T>(url: string, csrfToken?: string): ResourceState<T> & {
|
|
10
|
+
refetch: () => Promise<void>;
|
|
11
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
export async function jsonRequest(url, init = {}) {
|
|
3
|
+
const { csrfToken, headers, ...rest } = init;
|
|
4
|
+
const res = await fetch(url, {
|
|
5
|
+
credentials: 'same-origin',
|
|
6
|
+
headers: {
|
|
7
|
+
'Accept': 'application/json',
|
|
8
|
+
...(rest.body ? { 'Content-Type': 'application/json' } : {}),
|
|
9
|
+
...(csrfToken ? { 'X-CSRF-TOKEN': csrfToken } : {}),
|
|
10
|
+
...headers,
|
|
11
|
+
},
|
|
12
|
+
...rest,
|
|
13
|
+
});
|
|
14
|
+
if (!res.ok) {
|
|
15
|
+
let message = `Request failed (${res.status})`;
|
|
16
|
+
try {
|
|
17
|
+
const body = await res.json();
|
|
18
|
+
if (body && typeof body.message === 'string')
|
|
19
|
+
message = body.message;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
}
|
|
23
|
+
throw new Error(message);
|
|
24
|
+
}
|
|
25
|
+
const text = await res.text();
|
|
26
|
+
return (text ? JSON.parse(text) : null);
|
|
27
|
+
}
|
|
28
|
+
export function useResource(url, csrfToken) {
|
|
29
|
+
const [state, setState] = useState({
|
|
30
|
+
data: null,
|
|
31
|
+
loading: true,
|
|
32
|
+
error: null,
|
|
33
|
+
});
|
|
34
|
+
const refetch = useCallback(async () => {
|
|
35
|
+
setState((s) => ({ ...s, loading: true, error: null }));
|
|
36
|
+
try {
|
|
37
|
+
const data = await jsonRequest(url, { csrfToken });
|
|
38
|
+
setState({ data, loading: false, error: null });
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
setState({ data: null, loading: false, error: err });
|
|
42
|
+
}
|
|
43
|
+
}, [url, csrfToken]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
void refetch();
|
|
46
|
+
}, [refetch]);
|
|
47
|
+
return { ...state, refetch };
|
|
48
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ResourceState } from './use_resource.js';
|
|
2
|
+
export interface AuthSession {
|
|
3
|
+
id: string;
|
|
4
|
+
device?: string;
|
|
5
|
+
ip?: string;
|
|
6
|
+
lastSeenAt?: string;
|
|
7
|
+
current?: boolean;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface UseSessionsResult extends ResourceState<AuthSession[]> {
|
|
11
|
+
actions: {
|
|
12
|
+
refetch(): Promise<void>;
|
|
13
|
+
revoke(id: string): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare function useSessions(): UseSessionsResult;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useAuthkitConfig } from '../config.js';
|
|
3
|
+
import { jsonRequest, useResource } from './use_resource.js';
|
|
4
|
+
export function useSessions() {
|
|
5
|
+
const config = useAuthkitConfig();
|
|
6
|
+
const { data, loading, error, refetch } = useResource(config.endpoints.sessions, config.csrfToken);
|
|
7
|
+
const revoke = useCallback(async (id) => {
|
|
8
|
+
await jsonRequest(`${config.endpoints.sessions}/${encodeURIComponent(id)}/revoke`, {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
csrfToken: config.csrfToken,
|
|
11
|
+
});
|
|
12
|
+
await refetch();
|
|
13
|
+
}, [config.endpoints.sessions, config.csrfToken, refetch]);
|
|
14
|
+
return { data, loading, error, actions: { refetch, revoke } };
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useAuthkitConfig, buildAuthUrl } from '../config.js';
|
|
3
|
+
import { currentUrl } from '../utils.js';
|
|
4
|
+
export function useSignIn() {
|
|
5
|
+
const config = useAuthkitConfig();
|
|
6
|
+
const signIn = useCallback((opts) => {
|
|
7
|
+
const returnTo = opts?.returnTo ?? currentUrl();
|
|
8
|
+
const url = buildAuthUrl(config.loginUrl, returnTo);
|
|
9
|
+
if (typeof window !== 'undefined')
|
|
10
|
+
window.location.assign(url);
|
|
11
|
+
}, [config.loginUrl]);
|
|
12
|
+
return { signIn };
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useAuthkitConfig, buildAuthUrl } from '../config.js';
|
|
3
|
+
export function useSignOut() {
|
|
4
|
+
const config = useAuthkitConfig();
|
|
5
|
+
const signOut = useCallback((opts) => {
|
|
6
|
+
const url = buildAuthUrl(config.logoutUrl, opts?.returnTo);
|
|
7
|
+
if (typeof window !== 'undefined')
|
|
8
|
+
window.location.assign(url);
|
|
9
|
+
}, [config.logoutUrl]);
|
|
10
|
+
return { signOut };
|
|
11
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import { useAuthkitConfig } from '../config.js';
|
|
3
|
+
import { jsonRequest } from './use_resource.js';
|
|
4
|
+
export function useSwitchOrganization() {
|
|
5
|
+
const config = useAuthkitConfig();
|
|
6
|
+
const base = config.endpoints.orgs.replace('/json', '');
|
|
7
|
+
const [loading, setLoading] = useState(false);
|
|
8
|
+
const [error, setError] = useState(null);
|
|
9
|
+
const activate = useCallback(async (orgId) => {
|
|
10
|
+
setLoading(true);
|
|
11
|
+
setError(null);
|
|
12
|
+
try {
|
|
13
|
+
await jsonRequest(`${base}/${encodeURIComponent(orgId)}/activate`, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
csrfToken: config.csrfToken,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
setError(err);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
setLoading(false);
|
|
23
|
+
}
|
|
24
|
+
}, [base, config.csrfToken]);
|
|
25
|
+
const deactivate = useCallback(async () => {
|
|
26
|
+
setLoading(true);
|
|
27
|
+
setError(null);
|
|
28
|
+
try {
|
|
29
|
+
await jsonRequest(`${base}/deactivate`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
csrfToken: config.csrfToken,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
setError(err);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
setLoading(false);
|
|
39
|
+
}
|
|
40
|
+
}, [base, config.csrfToken]);
|
|
41
|
+
return { loading, error, activate, deactivate };
|
|
42
|
+
}
|
package/build/src/use_auth.js
CHANGED
|
@@ -12,7 +12,7 @@ export function useAuth() {
|
|
|
12
12
|
const pageProps = usePage().props;
|
|
13
13
|
const resolved = contextValue ?? pageProps?.authkit;
|
|
14
14
|
if (!resolved && typeof process !== 'undefined' && process.env.NODE_ENV !== 'production') {
|
|
15
|
-
console.warn('[authkit] useAuth():
|
|
15
|
+
console.warn('[authkit] useAuth(): no <AuthProvider> nor shared-prop `authkit` found — returning unauthenticated state.');
|
|
16
16
|
}
|
|
17
17
|
const authkit = resolved ?? UNAUTHENTICATED;
|
|
18
18
|
return useMemo(() => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function deriveInitials(name, email) {
|
|
2
|
+
const source = (name ?? '').trim();
|
|
3
|
+
if (source) {
|
|
4
|
+
const parts = source.split(/\s+/).filter(Boolean);
|
|
5
|
+
if (parts.length === 1)
|
|
6
|
+
return parts[0].slice(0, 2).toUpperCase();
|
|
7
|
+
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
8
|
+
}
|
|
9
|
+
const e = (email ?? '').trim();
|
|
10
|
+
if (e)
|
|
11
|
+
return e[0].toUpperCase();
|
|
12
|
+
return '?';
|
|
13
|
+
}
|
|
14
|
+
export function currentUrl() {
|
|
15
|
+
if (typeof window === 'undefined')
|
|
16
|
+
return '';
|
|
17
|
+
return window.location.pathname + window.location.search;
|
|
18
|
+
}
|