@adonis-agora/authkit-react 0.13.1 → 0.15.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 +7 -19
- package/build/index.d.ts +7 -3
- package/build/index.js +4 -2
- package/build/src/authkit_provider.d.ts +0 -1
- package/build/src/components/passkey_button.d.ts +11 -0
- package/build/src/components/passkey_button.js +24 -0
- package/build/src/components/sign_in_button.d.ts +1 -1
- package/build/src/components/sign_out_button.d.ts +1 -1
- package/build/src/hooks/use_passkey_autofill.js +19 -26
- package/build/src/hooks/use_passkey_login.d.ts +12 -0
- package/build/src/hooks/use_passkey_login.js +25 -0
- package/build/src/passkey/authenticate.d.ts +22 -0
- package/build/src/passkey/authenticate.js +63 -0
- package/build/src/provider.d.ts +0 -2
- package/build/src/roles.d.ts +0 -3
- package/build/src/roles.js +0 -15
- package/build/src/types.d.ts +0 -4
- package/build/src/use_auth.js +1 -5
- package/package.json +1 -1
- package/styles.css +135 -31
- package/build/src/components/can.d.ts +0 -10
- package/build/src/components/can.js +0 -19
package/README.md
CHANGED
|
@@ -33,7 +33,6 @@ export default defineConfig({
|
|
|
33
33
|
return {
|
|
34
34
|
user: user ?? null,
|
|
35
35
|
globalRoles: auth.identity?.globalRoles ?? [],
|
|
36
|
-
// appRoles opcional, se o host resolver papéis de app
|
|
37
36
|
}
|
|
38
37
|
},
|
|
39
38
|
},
|
|
@@ -41,7 +40,7 @@ export default defineConfig({
|
|
|
41
40
|
```
|
|
42
41
|
|
|
43
42
|
O objeto `user` deve corresponder ao tipo `AuthUser`:
|
|
44
|
-
`{ id, email, name?, avatarUrl?, globalRoles
|
|
43
|
+
`{ id, email, name?, avatarUrl?, globalRoles }` — exatamente a saída de
|
|
45
44
|
`identityToUser`/`resolveUser` do client.
|
|
46
45
|
|
|
47
46
|
## 2. No frontend: `useAuth()`
|
|
@@ -69,7 +68,7 @@ não-autenticado (`user: null`, listas vazias).
|
|
|
69
68
|
## 3. Componentes de gating
|
|
70
69
|
|
|
71
70
|
```tsx
|
|
72
|
-
import { Authenticated, Guest
|
|
71
|
+
import { Authenticated, Guest } from '@adonis-agora/authkit-react'
|
|
73
72
|
|
|
74
73
|
<Authenticated fallback={<LoginButton />}>
|
|
75
74
|
<Dashboard />
|
|
@@ -78,23 +77,12 @@ import { Authenticated, Guest, Can } from '@adonis-agora/authkit-react'
|
|
|
78
77
|
<Guest>
|
|
79
78
|
<MarketingBanner />
|
|
80
79
|
</Guest>
|
|
81
|
-
|
|
82
|
-
{/* papel global único */}
|
|
83
|
-
<Can role="ADMIN">
|
|
84
|
-
<AdminPanel />
|
|
85
|
-
</Can>
|
|
86
|
-
|
|
87
|
-
{/* exige todos os papéis */}
|
|
88
|
-
<Can roles={['ADMIN', 'BILLING']} mode="all" fallback={<NoAccess />}>
|
|
89
|
-
<BillingSettings />
|
|
90
|
-
</Can>
|
|
91
|
-
|
|
92
|
-
{/* papel específico do app */}
|
|
93
|
-
<Can role="EDITOR" appRole>
|
|
94
|
-
<EditButton />
|
|
95
|
-
</Can>
|
|
96
80
|
```
|
|
97
81
|
|
|
82
|
+
Gating por papel/permissão de app (`<Can>`) migrou para
|
|
83
|
+
`@adonis-agora/authz-react`, que consulta o serviço Authz em vez de depender
|
|
84
|
+
de papéis de app resolvidos no host — veja a doc desse pacote.
|
|
85
|
+
|
|
98
86
|
## 4. `AuthProvider` (opcional)
|
|
99
87
|
|
|
100
88
|
Fora do Inertia (testes, Storybook), injete o valor manualmente. O contexto tem
|
|
@@ -139,7 +127,7 @@ redirecionam para o IdP. Temável via CSS vars `--authkit-*`. Veja a
|
|
|
139
127
|
Para uso fora de componentes, as funções de papéis são exportadas e livres de React:
|
|
140
128
|
|
|
141
129
|
```ts
|
|
142
|
-
import { hasGlobalRole, hasAnyGlobalRole, hasAllGlobalRoles
|
|
130
|
+
import { hasGlobalRole, hasAnyGlobalRole, hasAllGlobalRoles } from '@adonis-agora/authkit-react'
|
|
143
131
|
|
|
144
132
|
hasGlobalRole(user, 'ADMIN')
|
|
145
133
|
hasAnyGlobalRole(user, ['ADMIN', 'TEACHER'])
|
package/build/index.d.ts
CHANGED
|
@@ -29,13 +29,15 @@ export { jsonRequest, useResource } from "./src/hooks/use_resource.js";
|
|
|
29
29
|
export type { ResourceState } from "./src/hooks/use_resource.js";
|
|
30
30
|
export { usePasskeyAutofill } from "./src/hooks/use_passkey_autofill.js";
|
|
31
31
|
export type { UsePasskeyAutofillOptions } from "./src/hooks/use_passkey_autofill.js";
|
|
32
|
+
export { usePasskeyLogin } from "./src/hooks/use_passkey_login.js";
|
|
33
|
+
export type { UsePasskeyLoginOptions, UsePasskeyLoginResult, } from "./src/hooks/use_passkey_login.js";
|
|
34
|
+
export { authenticatePasskey, submitPasskeyVerification, loadStartAuthentication, } from "./src/passkey/authenticate.js";
|
|
35
|
+
export type { AuthenticatePasskeyOptions, SubmitPasskeyVerificationOptions, PasskeyCeremonyDeps, StartAuthenticationFn, } from "./src/passkey/authenticate.js";
|
|
32
36
|
export { usePasswordStrength, heuristicScorer, } from "./src/hooks/use_password_strength.js";
|
|
33
37
|
export type { PasswordStrengthScore, PasswordStrengthResult, PasswordScorer, UsePasswordStrengthOptions, } from "./src/hooks/use_password_strength.js";
|
|
34
38
|
export { deriveInitials, currentUrl } from "./src/utils.js";
|
|
35
39
|
export { Authenticated, Guest } from "./src/components/authenticated.js";
|
|
36
40
|
export type { AuthenticatedProps, GuestProps, } from "./src/components/authenticated.js";
|
|
37
|
-
export { Can } from "./src/components/can.js";
|
|
38
|
-
export type { CanProps } from "./src/components/can.js";
|
|
39
41
|
export { CanPermission } from "./src/components/can_permission.js";
|
|
40
42
|
export type { CanPermissionProps } from "./src/components/can_permission.js";
|
|
41
43
|
export { useCan, checkCan, invalidateCanCache } from "./src/hooks/use_can.js";
|
|
@@ -54,13 +56,15 @@ export { AuthorizedApps } from "./src/components/authorized_apps.js";
|
|
|
54
56
|
export type { AuthorizedAppsProps } from "./src/components/authorized_apps.js";
|
|
55
57
|
export { PasswordStrengthMeter } from "./src/components/password_strength_meter.js";
|
|
56
58
|
export type { PasswordStrengthMeterProps } from "./src/components/password_strength_meter.js";
|
|
59
|
+
export { PasskeyButton } from "./src/components/passkey_button.js";
|
|
60
|
+
export type { PasskeyButtonProps } from "./src/components/passkey_button.js";
|
|
57
61
|
export { OrganizationSwitcher } from "./src/components/organization_switcher.js";
|
|
58
62
|
export type { OrganizationSwitcherProps } from "./src/components/organization_switcher.js";
|
|
59
63
|
export { OrganizationProfile } from "./src/components/organization_profile.js";
|
|
60
64
|
export type { OrganizationProfileProps } from "./src/components/organization_profile.js";
|
|
61
65
|
export { KeyRotation } from "./src/components/key_rotation.js";
|
|
62
66
|
export type { KeyRotationProps } from "./src/components/key_rotation.js";
|
|
63
|
-
export { hasGlobalRole, hasAnyGlobalRole, hasAllGlobalRoles,
|
|
67
|
+
export { hasGlobalRole, hasAnyGlobalRole, hasAllGlobalRoles, } from "./src/roles.js";
|
|
64
68
|
export type { AuthUser, AuthSharedProps, AuthState } from "./src/types.js";
|
|
65
69
|
export { createAuthkitClient, AuthkitClientError, } from "./src/client/client.js";
|
|
66
70
|
export type { AuthkitClient, AuthkitClientOptions, } from "./src/client/client.js";
|
package/build/index.js
CHANGED
|
@@ -14,10 +14,11 @@ export { useSwitchOrganization } from "./src/hooks/use_switch_organization.js";
|
|
|
14
14
|
export { useOrgInvitations } from "./src/hooks/use_org_invitations.js";
|
|
15
15
|
export { jsonRequest, useResource } from "./src/hooks/use_resource.js";
|
|
16
16
|
export { usePasskeyAutofill } from "./src/hooks/use_passkey_autofill.js";
|
|
17
|
+
export { usePasskeyLogin } from "./src/hooks/use_passkey_login.js";
|
|
18
|
+
export { authenticatePasskey, submitPasskeyVerification, loadStartAuthentication, } from "./src/passkey/authenticate.js";
|
|
17
19
|
export { usePasswordStrength, heuristicScorer, } from "./src/hooks/use_password_strength.js";
|
|
18
20
|
export { deriveInitials, currentUrl } from "./src/utils.js";
|
|
19
21
|
export { Authenticated, Guest } from "./src/components/authenticated.js";
|
|
20
|
-
export { Can } from "./src/components/can.js";
|
|
21
22
|
export { CanPermission } from "./src/components/can_permission.js";
|
|
22
23
|
export { useCan, checkCan, invalidateCanCache } from "./src/hooks/use_can.js";
|
|
23
24
|
export { SignInButton } from "./src/components/sign_in_button.js";
|
|
@@ -27,10 +28,11 @@ export { UserButton } from "./src/components/user_button.js";
|
|
|
27
28
|
export { UserProfile } from "./src/components/user_profile.js";
|
|
28
29
|
export { AuthorizedApps } from "./src/components/authorized_apps.js";
|
|
29
30
|
export { PasswordStrengthMeter } from "./src/components/password_strength_meter.js";
|
|
31
|
+
export { PasskeyButton } from "./src/components/passkey_button.js";
|
|
30
32
|
export { OrganizationSwitcher } from "./src/components/organization_switcher.js";
|
|
31
33
|
export { OrganizationProfile } from "./src/components/organization_profile.js";
|
|
32
34
|
export { KeyRotation } from "./src/components/key_rotation.js";
|
|
33
|
-
export { hasGlobalRole, hasAnyGlobalRole, hasAllGlobalRoles,
|
|
35
|
+
export { hasGlobalRole, hasAnyGlobalRole, hasAllGlobalRoles, } from "./src/roles.js";
|
|
34
36
|
export { createAuthkitClient, AuthkitClientError, } from "./src/client/client.js";
|
|
35
37
|
export { AuthkitClientProvider, useAuthkitClient, createAuthkitQueryClient, } from "./src/client/context.js";
|
|
36
38
|
export { authkitKeys } from "./src/queries/keys.js";
|
|
@@ -9,5 +9,4 @@ export interface AuthkitProviderProps {
|
|
|
9
9
|
export declare function AuthkitProvider({ config, value, children }: AuthkitProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<{
|
|
10
10
|
user: import("./types.js").AuthUser | null;
|
|
11
11
|
globalRoles: string[];
|
|
12
|
-
appRoles?: string[];
|
|
13
12
|
} | undefined>> | import("react").FunctionComponentElement<import("react").ProviderProps<import("./config.js").ResolvedAuthkitConfig>>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
export interface PasskeyButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onClick"> {
|
|
3
|
+
optionsUrl: string;
|
|
4
|
+
verifyUrl: string;
|
|
5
|
+
csrfToken?: string;
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
errorContent?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
export declare function PasskeyButton({ optionsUrl, verifyUrl, csrfToken, children, errorContent, className, ...rest }: PasskeyButtonProps): import("react").DetailedReactHTMLElement<{
|
|
10
|
+
className: string;
|
|
11
|
+
}, HTMLElement>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createElement, } from "react";
|
|
2
|
+
import { usePasskeyLogin } from "../hooks/use_passkey_login.js";
|
|
3
|
+
const DEFAULT_ERROR = "Não foi possível autenticar com a passkey. Tente novamente.";
|
|
4
|
+
export function PasskeyButton({ optionsUrl, verifyUrl, csrfToken, children = "Entrar com passkey", errorContent, className, ...rest }) {
|
|
5
|
+
const { authenticate, busy, failed } = usePasskeyLogin({
|
|
6
|
+
optionsUrl,
|
|
7
|
+
verifyUrl,
|
|
8
|
+
csrfToken,
|
|
9
|
+
});
|
|
10
|
+
const button = createElement("button", {
|
|
11
|
+
type: "button",
|
|
12
|
+
className: ["authkit-button", "authkit-button--ghost", className]
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.join(" "),
|
|
15
|
+
onClick: () => {
|
|
16
|
+
void authenticate();
|
|
17
|
+
},
|
|
18
|
+
disabled: busy,
|
|
19
|
+
...rest,
|
|
20
|
+
}, children);
|
|
21
|
+
if (!failed || errorContent === null)
|
|
22
|
+
return button;
|
|
23
|
+
return createElement("div", { className: "authkit-passkey" }, button, createElement("p", { className: "authkit-passkey__error" }, errorContent ?? DEFAULT_ERROR));
|
|
24
|
+
}
|
|
@@ -13,6 +13,7 @@ export declare function SignInButton({ children, returnTo, showWhenAuthenticated
|
|
|
13
13
|
style?: import("react").CSSProperties | undefined;
|
|
14
14
|
title?: string | undefined | undefined;
|
|
15
15
|
role?: import("react").AriaRole | undefined;
|
|
16
|
+
hidden?: boolean | undefined | undefined;
|
|
16
17
|
resource?: string | undefined | undefined;
|
|
17
18
|
disabled?: boolean | undefined | undefined;
|
|
18
19
|
formAction?: string | ((formData: FormData) => void | Promise<void>) | undefined;
|
|
@@ -33,7 +34,6 @@ export declare function SignInButton({ children, returnTo, showWhenAuthenticated
|
|
|
33
34
|
dir?: string | undefined | undefined;
|
|
34
35
|
draggable?: (boolean | "true" | "false") | undefined;
|
|
35
36
|
enterKeyHint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
|
|
36
|
-
hidden?: boolean | undefined | undefined;
|
|
37
37
|
lang?: string | undefined | undefined;
|
|
38
38
|
nonce?: string | undefined | undefined;
|
|
39
39
|
spellCheck?: (boolean | "true" | "false") | undefined;
|
|
@@ -12,6 +12,7 @@ export declare function SignOutButton({ children, returnTo, className, ...rest }
|
|
|
12
12
|
style?: import("react").CSSProperties | undefined;
|
|
13
13
|
title?: string | undefined | undefined;
|
|
14
14
|
role?: import("react").AriaRole | undefined;
|
|
15
|
+
hidden?: boolean | undefined | undefined;
|
|
15
16
|
resource?: string | undefined | undefined;
|
|
16
17
|
disabled?: boolean | undefined | undefined;
|
|
17
18
|
formAction?: string | ((formData: FormData) => void | Promise<void>) | undefined;
|
|
@@ -32,7 +33,6 @@ export declare function SignOutButton({ children, returnTo, className, ...rest }
|
|
|
32
33
|
dir?: string | undefined | undefined;
|
|
33
34
|
draggable?: (boolean | "true" | "false") | undefined;
|
|
34
35
|
enterKeyHint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
|
|
35
|
-
hidden?: boolean | undefined | undefined;
|
|
36
36
|
lang?: string | undefined | undefined;
|
|
37
37
|
nonce?: string | undefined | undefined;
|
|
38
38
|
spellCheck?: (boolean | "true" | "false") | undefined;
|
|
@@ -1,19 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
-
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
-
});
|
|
6
|
-
}
|
|
7
|
-
return path;
|
|
8
|
-
};
|
|
9
|
-
import { useEffect, useRef } from 'react';
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { loadStartAuthentication, } from "../passkey/authenticate.js";
|
|
10
3
|
export function usePasskeyAutofill(options) {
|
|
11
|
-
const { optionsUrl, verifyUrl, onSuccess, csrfToken, enabled = true } = options;
|
|
4
|
+
const { optionsUrl, verifyUrl, onSuccess, csrfToken, enabled = true, } = options;
|
|
12
5
|
const abortRef = useRef(null);
|
|
13
6
|
useEffect(() => {
|
|
14
7
|
if (!enabled)
|
|
15
8
|
return;
|
|
16
|
-
if (typeof window ===
|
|
9
|
+
if (typeof window === "undefined" || typeof navigator === "undefined")
|
|
17
10
|
return;
|
|
18
11
|
if (!window.PublicKeyCredential)
|
|
19
12
|
return;
|
|
@@ -22,32 +15,28 @@ export function usePasskeyAutofill(options) {
|
|
|
22
15
|
abortRef.current = ac;
|
|
23
16
|
const run = async () => {
|
|
24
17
|
try {
|
|
25
|
-
const supported = typeof PublicKeyCredential
|
|
18
|
+
const supported = typeof PublicKeyCredential
|
|
19
|
+
.isConditionalMediationAvailable === "function"
|
|
26
20
|
? await PublicKeyCredential.isConditionalMediationAvailable()
|
|
27
21
|
: false;
|
|
28
22
|
if (!supported || cancelled)
|
|
29
23
|
return;
|
|
30
24
|
let startAuthentication;
|
|
31
25
|
try {
|
|
32
|
-
|
|
33
|
-
startAuthentication = mod.startAuthentication;
|
|
26
|
+
startAuthentication = await loadStartAuthentication();
|
|
34
27
|
}
|
|
35
28
|
catch {
|
|
36
|
-
|
|
37
|
-
const mod = await import(__rewriteRelativeImportExtension('https://cdn.jsdelivr.net/npm/@simplewebauthn/browser@13/dist/bundle/index.js'));
|
|
38
|
-
startAuthentication = mod.startAuthentication;
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
29
|
+
return;
|
|
43
30
|
}
|
|
44
|
-
if (
|
|
31
|
+
if (cancelled)
|
|
45
32
|
return;
|
|
46
|
-
const headers = {
|
|
33
|
+
const headers = {
|
|
34
|
+
"content-type": "application/json",
|
|
35
|
+
};
|
|
47
36
|
if (csrfToken)
|
|
48
|
-
headers[
|
|
37
|
+
headers["x-csrf-token"] = csrfToken;
|
|
49
38
|
const optRes = await fetch(optionsUrl, {
|
|
50
|
-
method:
|
|
39
|
+
method: "POST",
|
|
51
40
|
headers,
|
|
52
41
|
body: JSON.stringify({}),
|
|
53
42
|
signal: ac.signal,
|
|
@@ -58,7 +47,11 @@ export function usePasskeyAutofill(options) {
|
|
|
58
47
|
delete optionsJSON._discoverable;
|
|
59
48
|
if (cancelled)
|
|
60
49
|
return;
|
|
61
|
-
const assertion = await startAuthentication({
|
|
50
|
+
const assertion = await startAuthentication({
|
|
51
|
+
optionsJSON,
|
|
52
|
+
useBrowserAutofill: true,
|
|
53
|
+
verifyBrowserAutofillInput: true,
|
|
54
|
+
}, ac.signal);
|
|
62
55
|
if (cancelled)
|
|
63
56
|
return;
|
|
64
57
|
onSuccess(JSON.stringify(assertion));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface UsePasskeyLoginOptions {
|
|
2
|
+
optionsUrl: string;
|
|
3
|
+
verifyUrl: string;
|
|
4
|
+
csrfToken?: string;
|
|
5
|
+
onSuccess?: (assertion: string) => void;
|
|
6
|
+
}
|
|
7
|
+
export interface UsePasskeyLoginResult {
|
|
8
|
+
authenticate: () => Promise<void>;
|
|
9
|
+
busy: boolean;
|
|
10
|
+
failed: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function usePasskeyLogin(options: UsePasskeyLoginOptions): UsePasskeyLoginResult;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
import { authenticatePasskey, submitPasskeyVerification, } from "../passkey/authenticate.js";
|
|
3
|
+
export function usePasskeyLogin(options) {
|
|
4
|
+
const { optionsUrl, verifyUrl, csrfToken, onSuccess } = options;
|
|
5
|
+
const [busy, setBusy] = useState(false);
|
|
6
|
+
const [failed, setFailed] = useState(false);
|
|
7
|
+
const authenticate = useCallback(async () => {
|
|
8
|
+
setFailed(false);
|
|
9
|
+
setBusy(true);
|
|
10
|
+
try {
|
|
11
|
+
const assertion = await authenticatePasskey({ optionsUrl, csrfToken });
|
|
12
|
+
if (onSuccess) {
|
|
13
|
+
onSuccess(assertion);
|
|
14
|
+
setBusy(false);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
submitPasskeyVerification({ verifyUrl, assertion, csrfToken });
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
setFailed(true);
|
|
21
|
+
setBusy(false);
|
|
22
|
+
}
|
|
23
|
+
}, [optionsUrl, verifyUrl, csrfToken, onSuccess]);
|
|
24
|
+
return { authenticate, busy, failed };
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type StartAuthenticationFn = (opts: {
|
|
2
|
+
optionsJSON: unknown;
|
|
3
|
+
useBrowserAutofill?: boolean;
|
|
4
|
+
verifyBrowserAutofillInput?: boolean;
|
|
5
|
+
}, signal?: AbortSignal) => Promise<unknown>;
|
|
6
|
+
export declare function loadStartAuthentication(): Promise<StartAuthenticationFn>;
|
|
7
|
+
export interface AuthenticatePasskeyOptions {
|
|
8
|
+
optionsUrl: string;
|
|
9
|
+
csrfToken?: string;
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
}
|
|
12
|
+
export interface PasskeyCeremonyDeps {
|
|
13
|
+
fetch?: typeof globalThis.fetch;
|
|
14
|
+
loadStartAuthentication?: () => Promise<StartAuthenticationFn>;
|
|
15
|
+
}
|
|
16
|
+
export declare function authenticatePasskey(options: AuthenticatePasskeyOptions, deps?: PasskeyCeremonyDeps): Promise<string>;
|
|
17
|
+
export interface SubmitPasskeyVerificationOptions {
|
|
18
|
+
verifyUrl: string;
|
|
19
|
+
assertion: string;
|
|
20
|
+
csrfToken?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function submitPasskeyVerification(options: SubmitPasskeyVerificationOptions): void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
const CDN_URL = "https://cdn.jsdelivr.net/npm/@simplewebauthn/browser@13/dist/bundle/index.js";
|
|
10
|
+
export async function loadStartAuthentication() {
|
|
11
|
+
try {
|
|
12
|
+
const mod = await import(__rewriteRelativeImportExtension("@simplewebauthn/browser"));
|
|
13
|
+
return mod.startAuthentication;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
const mod = await import(__rewriteRelativeImportExtension(CDN_URL));
|
|
17
|
+
return mod.startAuthentication;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function authenticatePasskey(options, deps = {}) {
|
|
21
|
+
const { optionsUrl, csrfToken, signal } = options;
|
|
22
|
+
const doFetch = deps.fetch ?? globalThis.fetch;
|
|
23
|
+
const loadFn = deps.loadStartAuthentication ?? loadStartAuthentication;
|
|
24
|
+
const headers = {
|
|
25
|
+
"content-type": "application/json",
|
|
26
|
+
};
|
|
27
|
+
if (csrfToken)
|
|
28
|
+
headers["x-csrf-token"] = csrfToken;
|
|
29
|
+
const res = await doFetch(optionsUrl, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers,
|
|
32
|
+
body: JSON.stringify({}),
|
|
33
|
+
signal,
|
|
34
|
+
});
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
throw new Error(`passkey options request failed: ${res.status}`);
|
|
37
|
+
}
|
|
38
|
+
const optionsJSON = await res.json();
|
|
39
|
+
const startAuthentication = await loadFn();
|
|
40
|
+
const assertion = await startAuthentication({ optionsJSON }, signal);
|
|
41
|
+
return JSON.stringify(assertion);
|
|
42
|
+
}
|
|
43
|
+
export function submitPasskeyVerification(options) {
|
|
44
|
+
const { verifyUrl, assertion, csrfToken } = options;
|
|
45
|
+
if (typeof document === "undefined")
|
|
46
|
+
return;
|
|
47
|
+
const form = document.createElement("form");
|
|
48
|
+
form.method = "POST";
|
|
49
|
+
form.action = verifyUrl;
|
|
50
|
+
form.hidden = true;
|
|
51
|
+
if (csrfToken)
|
|
52
|
+
form.appendChild(hiddenInput("_csrf", csrfToken));
|
|
53
|
+
form.appendChild(hiddenInput("response", assertion));
|
|
54
|
+
document.body.appendChild(form);
|
|
55
|
+
form.submit();
|
|
56
|
+
}
|
|
57
|
+
function hiddenInput(name, value) {
|
|
58
|
+
const input = document.createElement("input");
|
|
59
|
+
input.type = "hidden";
|
|
60
|
+
input.name = name;
|
|
61
|
+
input.value = value;
|
|
62
|
+
return input;
|
|
63
|
+
}
|
package/build/src/provider.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ import type { AuthSharedProps } from './types.js';
|
|
|
3
3
|
export declare const AuthContext: import("react").Context<{
|
|
4
4
|
user: import("./types.js").AuthUser | null;
|
|
5
5
|
globalRoles: string[];
|
|
6
|
-
appRoles?: string[];
|
|
7
6
|
} | undefined>;
|
|
8
7
|
export interface AuthProviderProps {
|
|
9
8
|
value: AuthSharedProps['authkit'];
|
|
@@ -12,5 +11,4 @@ export interface AuthProviderProps {
|
|
|
12
11
|
export declare function AuthProvider({ value, children }: AuthProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<{
|
|
13
12
|
user: import("./types.js").AuthUser | null;
|
|
14
13
|
globalRoles: string[];
|
|
15
|
-
appRoles?: string[];
|
|
16
14
|
} | undefined>>;
|
package/build/src/roles.d.ts
CHANGED
|
@@ -2,6 +2,3 @@ import type { AuthUser } from './types.js';
|
|
|
2
2
|
export declare function hasGlobalRole(user: AuthUser | null | undefined, role: string): boolean;
|
|
3
3
|
export declare function hasAnyGlobalRole(user: AuthUser | null | undefined, roles: string[]): boolean;
|
|
4
4
|
export declare function hasAllGlobalRoles(user: AuthUser | null | undefined, roles: string[]): boolean;
|
|
5
|
-
export declare function hasAppRole(user: AuthUser | null | undefined, role: string): boolean;
|
|
6
|
-
export declare function hasAnyAppRole(user: AuthUser | null | undefined, roles: string[]): boolean;
|
|
7
|
-
export declare function hasAllAppRoles(user: AuthUser | null | undefined, roles: string[]): boolean;
|
package/build/src/roles.js
CHANGED
|
@@ -22,18 +22,3 @@ export function hasAllGlobalRoles(user, roles) {
|
|
|
22
22
|
return false;
|
|
23
23
|
return hasAllRoles(user.globalRoles, roles);
|
|
24
24
|
}
|
|
25
|
-
export function hasAppRole(user, role) {
|
|
26
|
-
if (!user || !user.appRoles)
|
|
27
|
-
return false;
|
|
28
|
-
return hasRole(user.appRoles, role);
|
|
29
|
-
}
|
|
30
|
-
export function hasAnyAppRole(user, roles) {
|
|
31
|
-
if (!user || !user.appRoles)
|
|
32
|
-
return false;
|
|
33
|
-
return hasAnyRole(user.appRoles, roles);
|
|
34
|
-
}
|
|
35
|
-
export function hasAllAppRoles(user, roles) {
|
|
36
|
-
if (!user || !user.appRoles)
|
|
37
|
-
return false;
|
|
38
|
-
return hasAllRoles(user.appRoles, roles);
|
|
39
|
-
}
|
package/build/src/types.d.ts
CHANGED
|
@@ -4,14 +4,12 @@ export interface AuthUser {
|
|
|
4
4
|
name?: string;
|
|
5
5
|
avatarUrl?: string;
|
|
6
6
|
globalRoles: string[];
|
|
7
|
-
appRoles?: string[];
|
|
8
7
|
[key: string]: unknown;
|
|
9
8
|
}
|
|
10
9
|
export interface AuthSharedProps {
|
|
11
10
|
authkit: {
|
|
12
11
|
user: AuthUser | null;
|
|
13
12
|
globalRoles: string[];
|
|
14
|
-
appRoles?: string[];
|
|
15
13
|
};
|
|
16
14
|
[key: string]: unknown;
|
|
17
15
|
}
|
|
@@ -19,9 +17,7 @@ export interface AuthState {
|
|
|
19
17
|
user: AuthUser | null;
|
|
20
18
|
isAuthenticated: boolean;
|
|
21
19
|
globalRoles: string[];
|
|
22
|
-
appRoles: string[];
|
|
23
20
|
hasGlobalRole(role: string): boolean;
|
|
24
21
|
hasAnyGlobalRole(roles: string[]): boolean;
|
|
25
22
|
hasAllGlobalRoles(roles: string[]): boolean;
|
|
26
|
-
hasAppRole(role: string): boolean;
|
|
27
23
|
}
|
package/build/src/use_auth.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { useContext, useMemo } from 'react';
|
|
2
2
|
import { usePage } from '@inertiajs/react';
|
|
3
3
|
import { AuthContext } from './provider.js';
|
|
4
|
-
import { hasAllGlobalRoles as hasAllGlobalRolesPure, hasAnyGlobalRole as hasAnyGlobalRolePure,
|
|
4
|
+
import { hasAllGlobalRoles as hasAllGlobalRolesPure, hasAnyGlobalRole as hasAnyGlobalRolePure, hasGlobalRole as hasGlobalRolePure, } from './roles.js';
|
|
5
5
|
const UNAUTHENTICATED = {
|
|
6
6
|
user: null,
|
|
7
7
|
globalRoles: [],
|
|
8
|
-
appRoles: undefined,
|
|
9
8
|
};
|
|
10
9
|
export function useAuth() {
|
|
11
10
|
const contextValue = useContext(AuthContext);
|
|
@@ -18,16 +17,13 @@ export function useAuth() {
|
|
|
18
17
|
return useMemo(() => {
|
|
19
18
|
const user = authkit.user ?? null;
|
|
20
19
|
const globalRoles = authkit.globalRoles ?? user?.globalRoles ?? [];
|
|
21
|
-
const appRoles = authkit.appRoles ?? user?.appRoles ?? [];
|
|
22
20
|
return {
|
|
23
21
|
user,
|
|
24
22
|
isAuthenticated: user !== null,
|
|
25
23
|
globalRoles,
|
|
26
|
-
appRoles,
|
|
27
24
|
hasGlobalRole: (role) => hasGlobalRolePure(user, role),
|
|
28
25
|
hasAnyGlobalRole: (roles) => hasAnyGlobalRolePure(user, roles),
|
|
29
26
|
hasAllGlobalRoles: (roles) => hasAllGlobalRolesPure(user, roles),
|
|
30
|
-
hasAppRole: (role) => hasAppRolePure(user, role),
|
|
31
27
|
};
|
|
32
28
|
}, [authkit]);
|
|
33
29
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis-agora/authkit-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Frontend ergonomics over AuthKit for AdonisJS + Inertia + React apps: a typed useAuth() hook, role-gating hooks and gating components.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dudousxd",
|
package/styles.css
CHANGED
|
@@ -47,6 +47,17 @@
|
|
|
47
47
|
border-color: var(--authkit-border);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
.authkit-passkey {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
gap: var(--authkit-gap);
|
|
54
|
+
}
|
|
55
|
+
.authkit-passkey__error {
|
|
56
|
+
margin: 0;
|
|
57
|
+
font-size: 0.85rem;
|
|
58
|
+
color: var(--authkit-danger);
|
|
59
|
+
}
|
|
60
|
+
|
|
50
61
|
.authkit-avatar {
|
|
51
62
|
display: inline-flex;
|
|
52
63
|
align-items: center;
|
|
@@ -206,16 +217,16 @@
|
|
|
206
217
|
transition: background-color 0.15s ease;
|
|
207
218
|
}
|
|
208
219
|
/* Cor por score, do mais fraco ao mais forte (via data-score no bar). */
|
|
209
|
-
.authkit-strength__bar[data-score=
|
|
220
|
+
.authkit-strength__bar[data-score="1"] .authkit-strength__segment--filled {
|
|
210
221
|
background: var(--authkit-danger);
|
|
211
222
|
}
|
|
212
|
-
.authkit-strength__bar[data-score=
|
|
223
|
+
.authkit-strength__bar[data-score="2"] .authkit-strength__segment--filled {
|
|
213
224
|
background: #f5a524;
|
|
214
225
|
}
|
|
215
|
-
.authkit-strength__bar[data-score=
|
|
226
|
+
.authkit-strength__bar[data-score="3"] .authkit-strength__segment--filled {
|
|
216
227
|
background: #d6c20a;
|
|
217
228
|
}
|
|
218
|
-
.authkit-strength__bar[data-score=
|
|
229
|
+
.authkit-strength__bar[data-score="4"] .authkit-strength__segment--filled {
|
|
219
230
|
background: #2fb344;
|
|
220
231
|
}
|
|
221
232
|
.authkit-strength__label {
|
|
@@ -246,8 +257,14 @@
|
|
|
246
257
|
font-size: 0.875rem;
|
|
247
258
|
color: var(--authkit-fg);
|
|
248
259
|
}
|
|
249
|
-
.authkit-orgswitcher__trigger:disabled {
|
|
250
|
-
|
|
260
|
+
.authkit-orgswitcher__trigger:disabled {
|
|
261
|
+
opacity: 0.6;
|
|
262
|
+
cursor: default;
|
|
263
|
+
}
|
|
264
|
+
.authkit-orgswitcher__chevron {
|
|
265
|
+
font-size: 0.65rem;
|
|
266
|
+
color: var(--authkit-muted);
|
|
267
|
+
}
|
|
251
268
|
.authkit-orgswitcher__menu {
|
|
252
269
|
position: absolute;
|
|
253
270
|
top: calc(100% + 4px);
|
|
@@ -256,7 +273,7 @@
|
|
|
256
273
|
background: var(--authkit-bg);
|
|
257
274
|
border: 1px solid var(--authkit-border);
|
|
258
275
|
border-radius: var(--authkit-radius);
|
|
259
|
-
box-shadow: 0 4px 16px rgba(0,0,0
|
|
276
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
|
260
277
|
z-index: 50;
|
|
261
278
|
overflow: hidden;
|
|
262
279
|
}
|
|
@@ -273,31 +290,118 @@
|
|
|
273
290
|
color: var(--authkit-fg);
|
|
274
291
|
text-align: left;
|
|
275
292
|
}
|
|
276
|
-
.authkit-orgswitcher__item:hover {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
.authkit-orgswitcher__item
|
|
293
|
+
.authkit-orgswitcher__item:hover {
|
|
294
|
+
background: color-mix(in srgb, var(--authkit-primary) 8%, transparent);
|
|
295
|
+
}
|
|
296
|
+
.authkit-orgswitcher__item--active {
|
|
297
|
+
font-weight: 600;
|
|
298
|
+
}
|
|
299
|
+
.authkit-orgswitcher__item-name {
|
|
300
|
+
flex: 1;
|
|
301
|
+
}
|
|
302
|
+
.authkit-orgswitcher__item-role {
|
|
303
|
+
font-size: 0.75rem;
|
|
304
|
+
color: var(--authkit-muted);
|
|
305
|
+
margin-left: 0.5rem;
|
|
306
|
+
}
|
|
280
307
|
|
|
281
308
|
/* ─── OrganizationProfile ──────────────────────────────────────────────── */
|
|
282
|
-
.authkit-org-profile {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
.authkit-org-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
.authkit-org-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
309
|
+
.authkit-org-profile {
|
|
310
|
+
display: flex;
|
|
311
|
+
flex-direction: column;
|
|
312
|
+
gap: var(--authkit-gap);
|
|
313
|
+
}
|
|
314
|
+
.authkit-org-profile__header {
|
|
315
|
+
margin-bottom: 0.5rem;
|
|
316
|
+
}
|
|
317
|
+
.authkit-org-profile__name {
|
|
318
|
+
font-weight: 600;
|
|
319
|
+
font-size: 1rem;
|
|
320
|
+
color: var(--authkit-fg);
|
|
321
|
+
}
|
|
322
|
+
.authkit-org-profile__slug {
|
|
323
|
+
font-size: 0.8rem;
|
|
324
|
+
color: var(--authkit-muted);
|
|
325
|
+
}
|
|
326
|
+
.authkit-org-profile__section-title {
|
|
327
|
+
font-size: 0.85rem;
|
|
328
|
+
font-weight: 600;
|
|
329
|
+
color: var(--authkit-fg);
|
|
330
|
+
margin-bottom: 0.4rem;
|
|
331
|
+
}
|
|
332
|
+
.authkit-org-profile__members {
|
|
333
|
+
display: flex;
|
|
334
|
+
flex-direction: column;
|
|
335
|
+
gap: 0.25rem;
|
|
336
|
+
}
|
|
337
|
+
.authkit-org-profile__member {
|
|
338
|
+
display: flex;
|
|
339
|
+
align-items: center;
|
|
340
|
+
gap: 0.5rem;
|
|
341
|
+
padding: 0.3rem 0;
|
|
342
|
+
border-bottom: 1px solid var(--authkit-border);
|
|
343
|
+
}
|
|
344
|
+
.authkit-org-profile__member:last-child {
|
|
345
|
+
border-bottom: none;
|
|
346
|
+
}
|
|
347
|
+
.authkit-org-profile__member-info {
|
|
348
|
+
display: flex;
|
|
349
|
+
flex-direction: column;
|
|
350
|
+
}
|
|
351
|
+
.authkit-org-profile__member-email {
|
|
352
|
+
font-size: 0.875rem;
|
|
353
|
+
color: var(--authkit-fg);
|
|
354
|
+
}
|
|
355
|
+
.authkit-org-profile__member-role {
|
|
356
|
+
font-size: 0.75rem;
|
|
357
|
+
color: var(--authkit-muted);
|
|
358
|
+
}
|
|
359
|
+
.authkit-org-profile__invite-form {
|
|
360
|
+
display: flex;
|
|
361
|
+
flex-direction: column;
|
|
362
|
+
gap: 0.4rem;
|
|
363
|
+
margin-top: 0.75rem;
|
|
364
|
+
}
|
|
365
|
+
.authkit-org-profile__empty {
|
|
366
|
+
font-size: 0.875rem;
|
|
367
|
+
color: var(--authkit-muted);
|
|
368
|
+
}
|
|
295
369
|
|
|
296
370
|
/* ─── KeyRotation ──────────────────────────────────────────────────────────── */
|
|
297
|
-
.authkit-keys {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
.authkit-
|
|
303
|
-
|
|
371
|
+
.authkit-keys {
|
|
372
|
+
display: flex;
|
|
373
|
+
flex-direction: column;
|
|
374
|
+
gap: var(--authkit-gap);
|
|
375
|
+
}
|
|
376
|
+
.authkit-keys__title {
|
|
377
|
+
font-size: 1rem;
|
|
378
|
+
font-weight: 600;
|
|
379
|
+
color: var(--authkit-fg);
|
|
380
|
+
margin: 0 0 0.5rem;
|
|
381
|
+
}
|
|
382
|
+
.authkit-keys__stats {
|
|
383
|
+
display: grid;
|
|
384
|
+
grid-template-columns: max-content 1fr;
|
|
385
|
+
gap: 0.2rem 0.75rem;
|
|
386
|
+
margin: 0 0 0.5rem;
|
|
387
|
+
font-size: 0.875rem;
|
|
388
|
+
}
|
|
389
|
+
.authkit-keys__stats dt {
|
|
390
|
+
color: var(--authkit-muted);
|
|
391
|
+
}
|
|
392
|
+
.authkit-keys__stats dd {
|
|
393
|
+
margin: 0;
|
|
394
|
+
color: var(--authkit-fg);
|
|
395
|
+
}
|
|
396
|
+
.authkit-keys__retire {
|
|
397
|
+
display: flex;
|
|
398
|
+
align-items: center;
|
|
399
|
+
gap: 0.4rem;
|
|
400
|
+
font-size: 0.875rem;
|
|
401
|
+
color: var(--authkit-fg);
|
|
402
|
+
cursor: pointer;
|
|
403
|
+
}
|
|
404
|
+
.authkit-keys__loading {
|
|
405
|
+
color: var(--authkit-muted);
|
|
406
|
+
font-size: 0.9rem;
|
|
407
|
+
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type ReactNode } from 'react';
|
|
2
|
-
export interface CanProps {
|
|
3
|
-
children: ReactNode;
|
|
4
|
-
role?: string;
|
|
5
|
-
roles?: string[];
|
|
6
|
-
mode?: 'any' | 'all';
|
|
7
|
-
appRole?: boolean;
|
|
8
|
-
fallback?: ReactNode;
|
|
9
|
-
}
|
|
10
|
-
export declare function Can({ children, role, roles, mode, appRole, fallback, }: CanProps): import("react").FunctionComponentElement<import("react").FragmentProps>;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { createElement, Fragment } from 'react';
|
|
2
|
-
import { useAuth } from '../use_auth.js';
|
|
3
|
-
import { hasAllAppRoles, hasAllGlobalRoles, hasAnyAppRole, hasAnyGlobalRole, hasAppRole, hasGlobalRole, } from '../roles.js';
|
|
4
|
-
export function Can({ children, role, roles, mode = 'any', appRole = false, fallback = null, }) {
|
|
5
|
-
const { user } = useAuth();
|
|
6
|
-
let allowed = false;
|
|
7
|
-
if (role !== undefined) {
|
|
8
|
-
allowed = appRole ? hasAppRole(user, role) : hasGlobalRole(user, role);
|
|
9
|
-
}
|
|
10
|
-
else if (roles !== undefined) {
|
|
11
|
-
if (appRole) {
|
|
12
|
-
allowed = mode === 'all' ? hasAllAppRoles(user, roles) : hasAnyAppRole(user, roles);
|
|
13
|
-
}
|
|
14
|
-
else {
|
|
15
|
-
allowed = mode === 'all' ? hasAllGlobalRoles(user, roles) : hasAnyGlobalRole(user, roles);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return createElement(Fragment, null, allowed ? children : fallback);
|
|
19
|
-
}
|