@structured-id/ui-core 0.1.5
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/LICENSE +661 -0
- package/README.md +47 -0
- package/dist/browser-vp/index.cjs +349 -0
- package/dist/browser-vp/index.d.cts +287 -0
- package/dist/browser-vp/index.d.ts +287 -0
- package/dist/browser-vp/index.js +349 -0
- package/dist/chunk-C3Q766CY.cjs +125 -0
- package/dist/chunk-J5YXRXB5.js +125 -0
- package/dist/index.cjs +19360 -0
- package/dist/index.d.cts +9536 -0
- package/dist/index.d.ts +9536 -0
- package/dist/index.js +19360 -0
- package/dist/storage-4CB2JTLW.cjs +22 -0
- package/dist/storage-THRWREZP.js +22 -0
- package/package.json +93 -0
- package/src/browser-vp/crypto.ts +254 -0
- package/src/browser-vp/index.test.ts +10 -0
- package/src/browser-vp/index.ts +118 -0
- package/src/browser-vp/prf.ts +181 -0
- package/src/browser-vp/storage.test.ts +9 -0
- package/src/browser-vp/storage.ts +165 -0
- package/src/browser-vp/types.ts +97 -0
- package/src/browser-vp/vp.test.ts +43 -0
- package/src/browser-vp/vp.ts +113 -0
- package/src/components/SidAvatar.vue +106 -0
- package/src/components/SidConfirmDialog.vue +125 -0
- package/src/components/SidStatusBadge.vue +71 -0
- package/src/components/index.ts +5 -0
- package/src/composables/index.ts +10 -0
- package/src/composables/useAuth.ts +40 -0
- package/src/composables/useBffAuth.ts +147 -0
- package/src/composables/useGrpc.test.ts +196 -0
- package/src/composables/useGrpc.ts +120 -0
- package/src/composables/useSession.ts +80 -0
- package/src/generated/google/api/http.ts +687 -0
- package/src/generated/google/protobuf/descriptor.ts +4865 -0
- package/src/generated/google/protobuf/duration.ts +230 -0
- package/src/generated/google/protobuf/empty.ts +94 -0
- package/src/generated/google/protobuf/struct.ts +484 -0
- package/src/generated/google/protobuf/timestamp.ts +287 -0
- package/src/generated/sid/v1/account/account.client.ts +527 -0
- package/src/generated/sid/v1/account/account.ts +3261 -0
- package/src/generated/sid/v1/admin/admin.client.ts +136 -0
- package/src/generated/sid/v1/admin/admin.ts +846 -0
- package/src/generated/sid/v1/admin/admin_roles.client.ts +181 -0
- package/src/generated/sid/v1/admin/admin_roles.ts +1606 -0
- package/src/generated/sid/v1/admin/analytics.client.ts +106 -0
- package/src/generated/sid/v1/admin/analytics.ts +1017 -0
- package/src/generated/sid/v1/admin/branding.client.ts +166 -0
- package/src/generated/sid/v1/admin/branding.ts +1906 -0
- package/src/generated/sid/v1/admin/enrollment.client.ts +165 -0
- package/src/generated/sid/v1/admin/enrollment.ts +1704 -0
- package/src/generated/sid/v1/admin/events.client.ts +195 -0
- package/src/generated/sid/v1/admin/events.ts +1900 -0
- package/src/generated/sid/v1/admin/flow.client.ts +183 -0
- package/src/generated/sid/v1/admin/flow.ts +1552 -0
- package/src/generated/sid/v1/admin/keys.client.ts +107 -0
- package/src/generated/sid/v1/admin/keys.ts +931 -0
- package/src/generated/sid/v1/admin/login_experience.client.ts +160 -0
- package/src/generated/sid/v1/admin/login_experience.ts +1061 -0
- package/src/generated/sid/v1/admin/migration.client.ts +127 -0
- package/src/generated/sid/v1/admin/migration.ts +1948 -0
- package/src/generated/sid/v1/admin/notifications.client.ts +128 -0
- package/src/generated/sid/v1/admin/notifications.ts +1332 -0
- package/src/generated/sid/v1/admin/operations.client.ts +241 -0
- package/src/generated/sid/v1/admin/operations.ts +2648 -0
- package/src/generated/sid/v1/admin/organizations.client.ts +57 -0
- package/src/generated/sid/v1/admin/organizations.ts +349 -0
- package/src/generated/sid/v1/admin/scopes.client.ts +171 -0
- package/src/generated/sid/v1/admin/scopes.ts +1324 -0
- package/src/generated/sid/v1/admin/security.client.ts +412 -0
- package/src/generated/sid/v1/admin/security.ts +4086 -0
- package/src/generated/sid/v1/admin/sessions.client.ts +114 -0
- package/src/generated/sid/v1/admin/sessions.ts +923 -0
- package/src/generated/sid/v1/admin/settings.client.ts +340 -0
- package/src/generated/sid/v1/admin/settings.ts +2395 -0
- package/src/generated/sid/v1/admin/user_profile.client.ts +57 -0
- package/src/generated/sid/v1/admin/user_profile.ts +730 -0
- package/src/generated/sid/v1/admin/webhooks.client.ts +269 -0
- package/src/generated/sid/v1/admin/webhooks.ts +2804 -0
- package/src/generated/sid/v1/attestation/attestation.client.ts +115 -0
- package/src/generated/sid/v1/attestation/attestation.ts +813 -0
- package/src/generated/sid/v1/authn/auth.client.ts +900 -0
- package/src/generated/sid/v1/authn/auth.ts +9287 -0
- package/src/generated/sid/v1/authn/flows.client.ts +413 -0
- package/src/generated/sid/v1/authn/flows.ts +3217 -0
- package/src/generated/sid/v1/authz/authz.client.ts +459 -0
- package/src/generated/sid/v1/authz/authz.ts +5343 -0
- package/src/generated/sid/v1/authz/governance.client.ts +102 -0
- package/src/generated/sid/v1/authz/governance.ts +818 -0
- package/src/generated/sid/v1/common/errors.ts +184 -0
- package/src/generated/sid/v1/common/pagination.ts +152 -0
- package/src/generated/sid/v1/common/transport.ts +227 -0
- package/src/generated/sid/v1/common/types.ts +151 -0
- package/src/generated/sid/v1/events/event_stream.client.ts +67 -0
- package/src/generated/sid/v1/events/event_stream.ts +374 -0
- package/src/generated/sid/v1/federation/upstream.client.ts +142 -0
- package/src/generated/sid/v1/federation/upstream.ts +1736 -0
- package/src/generated/sid/v1/identity/identity.client.ts +467 -0
- package/src/generated/sid/v1/identity/identity.ts +5613 -0
- package/src/generated/sid/v1/machine/machine_user.client.ts +219 -0
- package/src/generated/sid/v1/machine/machine_user.ts +2400 -0
- package/src/generated/sid/v1/machine/pat.client.ts +147 -0
- package/src/generated/sid/v1/machine/pat.ts +1268 -0
- package/src/generated/sid/v1/projects/projects.client.ts +286 -0
- package/src/generated/sid/v1/projects/projects.ts +2714 -0
- package/src/generated/sid/v1/scim/scim.client.ts +274 -0
- package/src/generated/sid/v1/scim/scim.ts +3043 -0
- package/src/generated/sid/v1/test/test.client.ts +232 -0
- package/src/generated/sid/v1/test/test.ts +1136 -0
- package/src/i18n/index.ts +40 -0
- package/src/index.ts +157 -0
- package/src/profile/auth.ts +15 -0
- package/src/profile/components/ConsentManager.vue +88 -0
- package/src/profile/components/ProfileEditor.vue +146 -0
- package/src/profile/components/SecuritySettings.vue +170 -0
- package/src/profile/components/SessionList.vue +100 -0
- package/src/profile/components/SidLoginForm.test.ts +80 -0
- package/src/profile/components/SidLoginForm.vue +230 -0
- package/src/profile/components/SidMfaChallenge.test.ts +127 -0
- package/src/profile/components/SidMfaChallenge.vue +243 -0
- package/src/profile/components/SidRegistrationForm.test.ts +79 -0
- package/src/profile/components/SidRegistrationForm.vue +317 -0
- package/src/profile/components/index.ts +11 -0
- package/src/profile/composables/index.ts +6 -0
- package/src/profile/composables/useAccountApi.ts +203 -0
- package/src/profile/composables/useAuthApi.opaque.test.ts +140 -0
- package/src/profile/composables/useAuthApi.ts +617 -0
- package/src/profile/composables/useIdentityApi.ts +123 -0
- package/src/profile/composables/useLoginSuccess.test.ts +112 -0
- package/src/profile/composables/useLoginSuccess.ts +65 -0
- package/src/profile/composables/useOpaqueAuth.test.ts +290 -0
- package/src/profile/composables/useOpaqueAuth.ts +191 -0
- package/src/profile/composables/useSecurityLevel.test.ts +198 -0
- package/src/profile/composables/useSecurityLevel.ts +324 -0
- package/src/profile/index.ts +18 -0
- package/src/quasar/SidPrincipalInput.vue +209 -0
- package/src/quasar/index.ts +8 -0
- package/src/stores/auth.test.ts +83 -0
- package/src/stores/auth.ts +92 -0
- package/src/stores/index.ts +1 -0
- package/src/transport/adaptive.test.ts +728 -0
- package/src/transport/adaptive.ts +308 -0
- package/src/transport/framing.test.ts +227 -0
- package/src/transport/framing.ts +98 -0
- package/src/transport/webtransport.test.ts +728 -0
- package/src/transport/webtransport.ts +323 -0
- package/src/types/auth.ts +58 -0
- package/src/types/index.ts +1 -0
- package/src/utils/formatters.test.ts +26 -0
- package/src/utils/formatters.ts +69 -0
- package/src/utils/index.ts +16 -0
- package/src/utils/normalizePrincipal.test.ts +188 -0
- package/src/utils/normalizePrincipal.ts +159 -0
- package/src/utils/validators.test.ts +75 -0
- package/src/utils/validators.ts +36 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* i18n key constants shared across all SID UI applications.
|
|
3
|
+
*
|
|
4
|
+
* These match the keys defined in sid-i18n Rust crate (locales/*.json).
|
|
5
|
+
* Apps import these constants instead of hardcoding strings,
|
|
6
|
+
* ensuring consistency between frontend and backend translations.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const AUTH_KEYS = {
|
|
10
|
+
LOGIN_TITLE: "auth.login.title",
|
|
11
|
+
LOGIN_SUBMIT: "auth.login.submit",
|
|
12
|
+
REGISTER_TITLE: "auth.register.title",
|
|
13
|
+
REGISTER_SUBMIT: "auth.register.submit",
|
|
14
|
+
MFA_TITLE: "auth.mfa.title",
|
|
15
|
+
MAGIC_LINK_TITLE: "auth.magic_link.title",
|
|
16
|
+
DEVICE_AUTH_TITLE: "auth.device_auth.title",
|
|
17
|
+
PASSWORD_RESET_TITLE: "auth.password_reset.title",
|
|
18
|
+
LOGOUT_TITLE: "auth.logout.title",
|
|
19
|
+
LOGOUT_SUBMIT: "auth.logout.submit",
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
export const ERROR_KEYS = {
|
|
23
|
+
INVALID_CREDENTIALS: "errors.auth.invalid_credentials",
|
|
24
|
+
ACCOUNT_LOCKED: "errors.auth.account_locked",
|
|
25
|
+
SESSION_EXPIRED: "errors.auth.session_expired",
|
|
26
|
+
MFA_REQUIRED: "errors.auth.mfa_required",
|
|
27
|
+
PERMISSION_DENIED: "errors.rbac.permission_denied",
|
|
28
|
+
NOT_FOUND: "errors.general.not_found",
|
|
29
|
+
INTERNAL_ERROR: "errors.general.internal_error",
|
|
30
|
+
RATE_LIMITED: "errors.general.rate_limited",
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
export const COMMON_KEYS = {
|
|
34
|
+
SAVE: "common.buttons.save",
|
|
35
|
+
CANCEL: "common.buttons.cancel",
|
|
36
|
+
DELETE: "common.buttons.delete",
|
|
37
|
+
CONFIRM: "common.buttons.confirm",
|
|
38
|
+
LOADING: "common.labels.loading",
|
|
39
|
+
NO_RESULTS: "common.labels.no_results",
|
|
40
|
+
} as const;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Composables
|
|
2
|
+
export { useAuth } from "./composables/useAuth";
|
|
3
|
+
export {
|
|
4
|
+
useGrpc,
|
|
5
|
+
initGrpc,
|
|
6
|
+
getTransport,
|
|
7
|
+
closeTransport,
|
|
8
|
+
getTransportType,
|
|
9
|
+
type TransportType,
|
|
10
|
+
} from "./composables/useGrpc";
|
|
11
|
+
export { useSession, type SessionCallbacks } from "./composables/useSession";
|
|
12
|
+
export {
|
|
13
|
+
useBffAuth,
|
|
14
|
+
initBff,
|
|
15
|
+
getBffBaseUrl,
|
|
16
|
+
checkBffSession,
|
|
17
|
+
startBffLogin,
|
|
18
|
+
bffLogout,
|
|
19
|
+
type BffUserInfo,
|
|
20
|
+
} from "./composables/useBffAuth";
|
|
21
|
+
|
|
22
|
+
// Stores
|
|
23
|
+
export { useAuthStore } from "./stores/auth";
|
|
24
|
+
|
|
25
|
+
// Components — import from '@structured-id/ui-core/components' (raw .vue SFC)
|
|
26
|
+
// Consuming apps with their own Vue build pipeline import directly:
|
|
27
|
+
// import { SidStatusBadge } from '@structured-id/ui-core/components'
|
|
28
|
+
|
|
29
|
+
// Utils
|
|
30
|
+
export {
|
|
31
|
+
isValidEmail,
|
|
32
|
+
isValidPhone,
|
|
33
|
+
isValidUuid,
|
|
34
|
+
isValidUsername,
|
|
35
|
+
minLength,
|
|
36
|
+
maxLength,
|
|
37
|
+
isRequired,
|
|
38
|
+
formatDate,
|
|
39
|
+
formatRelativeTime,
|
|
40
|
+
truncate,
|
|
41
|
+
maskEmail,
|
|
42
|
+
} from "./utils";
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
detectLoginInputType,
|
|
46
|
+
normalizePrincipal,
|
|
47
|
+
type LoginInputType,
|
|
48
|
+
type NormalizedPrincipal,
|
|
49
|
+
} from "./utils/normalizePrincipal";
|
|
50
|
+
|
|
51
|
+
// Types
|
|
52
|
+
export type { AuthState, SessionInfo, GrpcConfig } from "./types";
|
|
53
|
+
|
|
54
|
+
// i18n key constants
|
|
55
|
+
export { AUTH_KEYS, ERROR_KEYS, COMMON_KEYS } from "./i18n";
|
|
56
|
+
|
|
57
|
+
// Generated proto clients and types
|
|
58
|
+
export { AuthServiceClient } from "./generated/sid/v1/authn/auth.client";
|
|
59
|
+
export { IdentityServiceClient } from "./generated/sid/v1/identity/identity.client";
|
|
60
|
+
export { AccountServiceClient } from "./generated/sid/v1/account/account.client";
|
|
61
|
+
export { UpstreamServiceClient } from "./generated/sid/v1/federation/upstream.client";
|
|
62
|
+
|
|
63
|
+
// Re-export commonly used proto types
|
|
64
|
+
export type {
|
|
65
|
+
OpaqueLoginStartRequest,
|
|
66
|
+
OpaqueLoginStartResponse,
|
|
67
|
+
OpaqueLoginFinishRequest,
|
|
68
|
+
OpaqueLoginFinishResponse,
|
|
69
|
+
OpaqueRegistrationStartRequest,
|
|
70
|
+
OpaqueRegistrationStartResponse,
|
|
71
|
+
OpaqueRegistrationFinishRequest,
|
|
72
|
+
OpaqueRegistrationFinishResponse,
|
|
73
|
+
MfaVerifyResponse,
|
|
74
|
+
TotpEnrollmentChallenge,
|
|
75
|
+
FinishTotpEnrollmentResponse,
|
|
76
|
+
ResendSmsMfaResponse,
|
|
77
|
+
GenerateRecoveryCodesResponse,
|
|
78
|
+
VerifyOtpResponse,
|
|
79
|
+
ResendOtpResponse,
|
|
80
|
+
VerifyPhoneResponse,
|
|
81
|
+
ConfirmIdpLinkResponse,
|
|
82
|
+
UserOrganization,
|
|
83
|
+
ListUserOrganizationsResponse,
|
|
84
|
+
SelectOrganizationResponse,
|
|
85
|
+
GetTermsResponse,
|
|
86
|
+
AcceptTermsResponse,
|
|
87
|
+
CibaConsentResponse,
|
|
88
|
+
VerifyCaptchaResponse,
|
|
89
|
+
DynamicPrompt,
|
|
90
|
+
DynamicPromptField,
|
|
91
|
+
SubmitDynamicPromptResponse,
|
|
92
|
+
StepUpChallenge,
|
|
93
|
+
CompleteStepUpResponse,
|
|
94
|
+
DeleteMfaCredentialResponse,
|
|
95
|
+
ConfirmAccountDeletionResponse,
|
|
96
|
+
SubmitDeviceUserCodeResponse,
|
|
97
|
+
OAuth2AuthorizeResponse,
|
|
98
|
+
WebAuthnAuthenticationStartResponse,
|
|
99
|
+
WebAuthnAuthenticationFinishResponse,
|
|
100
|
+
WebAuthnRegistrationStartResponse,
|
|
101
|
+
WebAuthnRegistrationFinishResponse,
|
|
102
|
+
RequestMagicLinkResponse,
|
|
103
|
+
VerifyMagicLinkResponse,
|
|
104
|
+
RequestPasswordResetResponse,
|
|
105
|
+
VerifyPasswordResetResponse,
|
|
106
|
+
CompletePasswordResetResponse,
|
|
107
|
+
} from "./generated/sid/v1/authn/auth";
|
|
108
|
+
|
|
109
|
+
export type {
|
|
110
|
+
Profile,
|
|
111
|
+
Session,
|
|
112
|
+
Credential,
|
|
113
|
+
Principal,
|
|
114
|
+
} from "./generated/sid/v1/identity/identity";
|
|
115
|
+
|
|
116
|
+
export { StepUpMethod } from "./generated/sid/v1/authn/auth";
|
|
117
|
+
|
|
118
|
+
export {
|
|
119
|
+
ProfileStatus,
|
|
120
|
+
PrincipalType,
|
|
121
|
+
CredentialType,
|
|
122
|
+
AuthenticatorAttachment,
|
|
123
|
+
} from "./generated/sid/v1/identity/identity";
|
|
124
|
+
|
|
125
|
+
export type {
|
|
126
|
+
ConsentInfo,
|
|
127
|
+
ClaimConsent,
|
|
128
|
+
ActivityEvent,
|
|
129
|
+
ListConsentsResponse,
|
|
130
|
+
GetConsentDetailResponse,
|
|
131
|
+
ListActivityEventsResponse,
|
|
132
|
+
LinkedAccount,
|
|
133
|
+
ListLinkedAccountsResponse,
|
|
134
|
+
LinkAccountResponse,
|
|
135
|
+
ReauthenticateLinkResponse,
|
|
136
|
+
AvailableApp,
|
|
137
|
+
ListAvailableAppsResponse,
|
|
138
|
+
LaunchAppResponse,
|
|
139
|
+
PasswordlessStatus,
|
|
140
|
+
DisablePasswordlessResponse,
|
|
141
|
+
} from "./generated/sid/v1/account/account";
|
|
142
|
+
|
|
143
|
+
export {
|
|
144
|
+
ConsentStatus,
|
|
145
|
+
ClaimType,
|
|
146
|
+
ClaimConsentStatus,
|
|
147
|
+
ActivityEventStatus,
|
|
148
|
+
LinkedAccountTrustCategory,
|
|
149
|
+
LinkedAccountStatus,
|
|
150
|
+
} from "./generated/sid/v1/account/account";
|
|
151
|
+
|
|
152
|
+
export type { EnabledProviderInfo } from "./generated/sid/v1/federation/upstream";
|
|
153
|
+
|
|
154
|
+
export { Timestamp } from "./generated/google/protobuf/timestamp";
|
|
155
|
+
|
|
156
|
+
// Browser VP — import from '@structured-id/ui-core/browser-vp'
|
|
157
|
+
// Separate entry point: WebAuthn PRF, IndexedDB, VP creation (browser-only APIs)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth metadata helper for profile-core composables.
|
|
3
|
+
*
|
|
4
|
+
* After BFF migration (#536i-3): returns empty metadata.
|
|
5
|
+
* The BFF proxy injects the Bearer token server-side from the httpOnly session cookie.
|
|
6
|
+
* Client JS never holds tokens — no authorization header sent from the browser.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get gRPC metadata for authenticated requests.
|
|
11
|
+
* In BFF mode: returns empty object — BFF adds Authorization: Bearer automatically.
|
|
12
|
+
*/
|
|
13
|
+
export function authMeta(): Record<string, string> {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<q-banner v-if="error" class="bg-negative text-white q-mb-md" rounded dense>
|
|
4
|
+
{{ error }}
|
|
5
|
+
</q-banner>
|
|
6
|
+
|
|
7
|
+
<div v-if="loading" class="text-center q-pa-lg">
|
|
8
|
+
<q-spinner-dots size="32px" color="primary" />
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div
|
|
12
|
+
v-else-if="consents.length === 0"
|
|
13
|
+
class="text-center text-grey q-pa-lg"
|
|
14
|
+
>
|
|
15
|
+
No connected applications
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div v-else class="row q-gutter-md">
|
|
19
|
+
<div
|
|
20
|
+
v-for="consent in consents"
|
|
21
|
+
:key="consent.siteId"
|
|
22
|
+
class="col-12 col-sm-6 col-md-4"
|
|
23
|
+
>
|
|
24
|
+
<q-card flat bordered>
|
|
25
|
+
<q-card-section class="row items-center q-gutter-sm">
|
|
26
|
+
<q-avatar size="32px" color="grey-3">
|
|
27
|
+
<img
|
|
28
|
+
v-if="consent.siteFavicon"
|
|
29
|
+
:src="consent.siteFavicon"
|
|
30
|
+
:alt="consent.siteName"
|
|
31
|
+
/>
|
|
32
|
+
<q-icon v-else name="sym_o_language" color="grey" />
|
|
33
|
+
</q-avatar>
|
|
34
|
+
<div class="col">
|
|
35
|
+
<div class="text-subtitle2">{{ consent.siteName }}</div>
|
|
36
|
+
<div class="text-caption text-grey">
|
|
37
|
+
Connected {{ formatDate(consent.connectedAt) }}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
<q-badge
|
|
41
|
+
:color="consent.status === activeStatus ? 'positive' : 'grey'"
|
|
42
|
+
:label="consent.status === activeStatus ? 'Active' : 'Inactive'"
|
|
43
|
+
/>
|
|
44
|
+
</q-card-section>
|
|
45
|
+
<q-separator />
|
|
46
|
+
<q-card-actions align="right">
|
|
47
|
+
<q-btn
|
|
48
|
+
flat
|
|
49
|
+
dense
|
|
50
|
+
label="Details"
|
|
51
|
+
color="primary"
|
|
52
|
+
@click="$emit('view-detail', consent.siteId)"
|
|
53
|
+
/>
|
|
54
|
+
<q-btn
|
|
55
|
+
flat
|
|
56
|
+
dense
|
|
57
|
+
label="Disconnect"
|
|
58
|
+
color="negative"
|
|
59
|
+
@click="$emit('disconnect', consent.siteId)"
|
|
60
|
+
/>
|
|
61
|
+
</q-card-actions>
|
|
62
|
+
</q-card>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</template>
|
|
67
|
+
|
|
68
|
+
<script setup lang="ts">
|
|
69
|
+
import { type ConsentInfo, ConsentStatus, Timestamp } from "../../index";
|
|
70
|
+
|
|
71
|
+
defineProps<{
|
|
72
|
+
consents: ConsentInfo[];
|
|
73
|
+
loading: boolean;
|
|
74
|
+
error: string | null;
|
|
75
|
+
}>();
|
|
76
|
+
|
|
77
|
+
defineEmits<{
|
|
78
|
+
"view-detail": [siteId: string];
|
|
79
|
+
disconnect: [siteId: string];
|
|
80
|
+
}>();
|
|
81
|
+
|
|
82
|
+
const activeStatus = ConsentStatus.ACTIVE;
|
|
83
|
+
|
|
84
|
+
function formatDate(ts?: { seconds: bigint; nanos: number }): string {
|
|
85
|
+
if (!ts) return "";
|
|
86
|
+
return Timestamp.toDate(ts).toLocaleDateString();
|
|
87
|
+
}
|
|
88
|
+
</script>
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<q-banner v-if="error" class="bg-negative text-white q-mb-md" rounded dense>
|
|
4
|
+
{{ error }}
|
|
5
|
+
</q-banner>
|
|
6
|
+
|
|
7
|
+
<div v-if="loading" class="text-center q-pa-lg">
|
|
8
|
+
<q-spinner-dots size="32px" color="primary" />
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<q-form
|
|
12
|
+
v-else
|
|
13
|
+
@submit.prevent="$emit('save', formData)"
|
|
14
|
+
class="q-gutter-md"
|
|
15
|
+
>
|
|
16
|
+
<q-input
|
|
17
|
+
v-model="formData.givenName"
|
|
18
|
+
label="Given name"
|
|
19
|
+
outlined
|
|
20
|
+
:disable="saving"
|
|
21
|
+
>
|
|
22
|
+
<template v-slot:prepend>
|
|
23
|
+
<q-icon name="sym_o_badge" />
|
|
24
|
+
</template>
|
|
25
|
+
</q-input>
|
|
26
|
+
|
|
27
|
+
<q-input
|
|
28
|
+
v-model="formData.familyName"
|
|
29
|
+
label="Family name"
|
|
30
|
+
outlined
|
|
31
|
+
:disable="saving"
|
|
32
|
+
>
|
|
33
|
+
<template v-slot:prepend>
|
|
34
|
+
<q-icon name="sym_o_badge" />
|
|
35
|
+
</template>
|
|
36
|
+
</q-input>
|
|
37
|
+
|
|
38
|
+
<q-input
|
|
39
|
+
v-model="formData.middleName"
|
|
40
|
+
label="Middle name"
|
|
41
|
+
outlined
|
|
42
|
+
:disable="saving"
|
|
43
|
+
>
|
|
44
|
+
<template v-slot:prepend>
|
|
45
|
+
<q-icon name="sym_o_badge" />
|
|
46
|
+
</template>
|
|
47
|
+
</q-input>
|
|
48
|
+
|
|
49
|
+
<q-input
|
|
50
|
+
v-if="showAvatar"
|
|
51
|
+
v-model="formData.avatarUrl"
|
|
52
|
+
label="Avatar URL"
|
|
53
|
+
outlined
|
|
54
|
+
:disable="saving"
|
|
55
|
+
>
|
|
56
|
+
<template v-slot:prepend>
|
|
57
|
+
<q-icon name="sym_o_account_circle" />
|
|
58
|
+
</template>
|
|
59
|
+
</q-input>
|
|
60
|
+
|
|
61
|
+
<slot name="extra-fields" :form="formData" />
|
|
62
|
+
|
|
63
|
+
<q-input
|
|
64
|
+
v-if="username"
|
|
65
|
+
:model-value="username"
|
|
66
|
+
label="Username"
|
|
67
|
+
outlined
|
|
68
|
+
readonly
|
|
69
|
+
disable
|
|
70
|
+
>
|
|
71
|
+
<template v-slot:prepend>
|
|
72
|
+
<q-icon name="sym_o_alternate_email" />
|
|
73
|
+
</template>
|
|
74
|
+
</q-input>
|
|
75
|
+
|
|
76
|
+
<q-btn
|
|
77
|
+
type="submit"
|
|
78
|
+
label="Save changes"
|
|
79
|
+
color="primary"
|
|
80
|
+
:loading="saving"
|
|
81
|
+
:disable="!hasChanges"
|
|
82
|
+
/>
|
|
83
|
+
</q-form>
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<script setup lang="ts">
|
|
88
|
+
import { reactive, computed, watch } from "vue";
|
|
89
|
+
import type { Profile } from "../../index";
|
|
90
|
+
|
|
91
|
+
const props = withDefaults(
|
|
92
|
+
defineProps<{
|
|
93
|
+
profile: Profile | null;
|
|
94
|
+
loading: boolean;
|
|
95
|
+
saving: boolean;
|
|
96
|
+
error: string | null;
|
|
97
|
+
username?: string;
|
|
98
|
+
showAvatar?: boolean;
|
|
99
|
+
}>(),
|
|
100
|
+
{
|
|
101
|
+
username: "",
|
|
102
|
+
showAvatar: true,
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
defineEmits<{
|
|
107
|
+
save: [
|
|
108
|
+
data: {
|
|
109
|
+
givenName: string;
|
|
110
|
+
familyName: string;
|
|
111
|
+
middleName: string;
|
|
112
|
+
avatarUrl: string;
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
}>();
|
|
116
|
+
|
|
117
|
+
const formData = reactive({
|
|
118
|
+
givenName: "",
|
|
119
|
+
familyName: "",
|
|
120
|
+
middleName: "",
|
|
121
|
+
avatarUrl: "",
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const hasChanges = computed(() => {
|
|
125
|
+
if (!props.profile) return false;
|
|
126
|
+
return (
|
|
127
|
+
formData.givenName !== (props.profile.givenName || "") ||
|
|
128
|
+
formData.familyName !== (props.profile.familyName || "") ||
|
|
129
|
+
formData.middleName !== (props.profile.middleName || "") ||
|
|
130
|
+
formData.avatarUrl !== (props.profile.avatarUrl || "")
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
watch(
|
|
135
|
+
() => props.profile,
|
|
136
|
+
(p) => {
|
|
137
|
+
if (p) {
|
|
138
|
+
formData.givenName = p.givenName || "";
|
|
139
|
+
formData.familyName = p.familyName || "";
|
|
140
|
+
formData.middleName = p.middleName || "";
|
|
141
|
+
formData.avatarUrl = p.avatarUrl || "";
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{ immediate: true },
|
|
145
|
+
);
|
|
146
|
+
</script>
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<q-banner v-if="error" class="bg-negative text-white q-mb-md" rounded dense>
|
|
4
|
+
{{ error }}
|
|
5
|
+
</q-banner>
|
|
6
|
+
|
|
7
|
+
<!-- Security & Recoverability overview -->
|
|
8
|
+
<div class="row q-gutter-md q-mb-md">
|
|
9
|
+
<q-card flat bordered class="col">
|
|
10
|
+
<q-card-section>
|
|
11
|
+
<div class="text-subtitle2">Security level</div>
|
|
12
|
+
<div :class="`text-${securityColor}`" class="text-h5">
|
|
13
|
+
{{ securityLabel }}
|
|
14
|
+
</div>
|
|
15
|
+
<q-linear-progress
|
|
16
|
+
:value="securityProgress"
|
|
17
|
+
:color="securityColor"
|
|
18
|
+
class="q-mt-sm"
|
|
19
|
+
/>
|
|
20
|
+
<div class="text-caption text-grey q-mt-xs">
|
|
21
|
+
{{ factorCount }} factor{{ factorCount !== 1 ? "s" : "" }},
|
|
22
|
+
{{ categories.size }} categor{{
|
|
23
|
+
categories.size !== 1 ? "ies" : "y"
|
|
24
|
+
}}
|
|
25
|
+
</div>
|
|
26
|
+
</q-card-section>
|
|
27
|
+
</q-card>
|
|
28
|
+
|
|
29
|
+
<q-card flat bordered class="col">
|
|
30
|
+
<q-card-section>
|
|
31
|
+
<div class="text-subtitle2">Recoverability</div>
|
|
32
|
+
<div :class="`text-${recoverabilityColor}`" class="text-h5">
|
|
33
|
+
{{ recoverabilityLabel }}
|
|
34
|
+
</div>
|
|
35
|
+
<q-linear-progress
|
|
36
|
+
:value="recoverabilityProgress"
|
|
37
|
+
:color="recoverabilityColor"
|
|
38
|
+
class="q-mt-sm"
|
|
39
|
+
/>
|
|
40
|
+
<div class="text-caption text-grey q-mt-xs">
|
|
41
|
+
{{ recoveryMethods.length }} recovery method{{
|
|
42
|
+
recoveryMethods.length !== 1 ? "s" : ""
|
|
43
|
+
}}
|
|
44
|
+
</div>
|
|
45
|
+
</q-card-section>
|
|
46
|
+
</q-card>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<!-- Credentials list -->
|
|
50
|
+
<div v-if="loading" class="text-center q-pa-lg">
|
|
51
|
+
<q-spinner-dots size="32px" color="primary" />
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<q-list v-else-if="credentials.length > 0" separator>
|
|
55
|
+
<q-item v-for="cred in credentials" :key="cred.id">
|
|
56
|
+
<q-item-section avatar>
|
|
57
|
+
<q-icon :name="credIcon(cred.type)" :color="credColor(cred.type)" />
|
|
58
|
+
</q-item-section>
|
|
59
|
+
<q-item-section>
|
|
60
|
+
<q-item-label>
|
|
61
|
+
{{ cred.label || credTypeName(cred.type) }}
|
|
62
|
+
</q-item-label>
|
|
63
|
+
<q-item-label caption>
|
|
64
|
+
{{ credTypeName(cred.type) }}
|
|
65
|
+
<span v-if="cred.lastUsedAt">
|
|
66
|
+
· Last used {{ formatRelative(cred.lastUsedAt) }}
|
|
67
|
+
</span>
|
|
68
|
+
</q-item-label>
|
|
69
|
+
</q-item-section>
|
|
70
|
+
<q-item-section side>
|
|
71
|
+
<div class="row q-gutter-xs">
|
|
72
|
+
<q-btn
|
|
73
|
+
flat
|
|
74
|
+
dense
|
|
75
|
+
icon="sym_o_edit"
|
|
76
|
+
@click="$emit('edit-label', cred)"
|
|
77
|
+
/>
|
|
78
|
+
<q-btn
|
|
79
|
+
flat
|
|
80
|
+
dense
|
|
81
|
+
icon="sym_o_delete"
|
|
82
|
+
color="negative"
|
|
83
|
+
@click="$emit('revoke', cred)"
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
</q-item-section>
|
|
87
|
+
</q-item>
|
|
88
|
+
</q-list>
|
|
89
|
+
|
|
90
|
+
<div v-else class="text-center text-grey q-pa-lg">
|
|
91
|
+
No credentials configured
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<slot name="actions" />
|
|
95
|
+
</div>
|
|
96
|
+
</template>
|
|
97
|
+
|
|
98
|
+
<script setup lang="ts">
|
|
99
|
+
import { type Credential, CredentialType, Timestamp } from "../../index";
|
|
100
|
+
import type { FactorCategory } from "../composables/useSecurityLevel";
|
|
101
|
+
|
|
102
|
+
defineProps<{
|
|
103
|
+
credentials: Credential[];
|
|
104
|
+
loading: boolean;
|
|
105
|
+
error: string | null;
|
|
106
|
+
securityLabel: string;
|
|
107
|
+
securityColor: string;
|
|
108
|
+
securityProgress: number;
|
|
109
|
+
factorCount: number;
|
|
110
|
+
categories: Set<FactorCategory>;
|
|
111
|
+
recoverabilityLabel: string;
|
|
112
|
+
recoverabilityColor: string;
|
|
113
|
+
recoverabilityProgress: number;
|
|
114
|
+
recoveryMethods: string[];
|
|
115
|
+
}>();
|
|
116
|
+
|
|
117
|
+
defineEmits<{
|
|
118
|
+
"edit-label": [cred: Credential];
|
|
119
|
+
revoke: [cred: Credential];
|
|
120
|
+
}>();
|
|
121
|
+
|
|
122
|
+
const CRED_ICONS: Partial<Record<CredentialType, string>> = {
|
|
123
|
+
[CredentialType.OPAQUE]: "sym_o_key",
|
|
124
|
+
[CredentialType.LEGACY_HASH]: "sym_o_key",
|
|
125
|
+
[CredentialType.TOTP]: "sym_o_timer",
|
|
126
|
+
[CredentialType.WEBAUTHN]: "sym_o_passkey",
|
|
127
|
+
[CredentialType.RECOVERY]: "sym_o_vpn_key",
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
function credIcon(type: CredentialType): string {
|
|
131
|
+
return CRED_ICONS[type] || "sym_o_security";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const CRED_COLORS: Partial<Record<CredentialType, string>> = {
|
|
135
|
+
[CredentialType.OPAQUE]: "blue",
|
|
136
|
+
[CredentialType.LEGACY_HASH]: "orange",
|
|
137
|
+
[CredentialType.TOTP]: "teal",
|
|
138
|
+
[CredentialType.WEBAUTHN]: "purple",
|
|
139
|
+
[CredentialType.RECOVERY]: "amber",
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
function credColor(type: CredentialType): string {
|
|
143
|
+
return CRED_COLORS[type] || "grey";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const CRED_TYPE_NAMES: Partial<Record<CredentialType, string>> = {
|
|
147
|
+
[CredentialType.OPAQUE]: "Password",
|
|
148
|
+
[CredentialType.LEGACY_HASH]: "Legacy password",
|
|
149
|
+
[CredentialType.TOTP]: "Authenticator app",
|
|
150
|
+
[CredentialType.WEBAUTHN]: "Passkey",
|
|
151
|
+
[CredentialType.RECOVERY]: "Recovery codes",
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
function credTypeName(type: CredentialType): string {
|
|
155
|
+
return CRED_TYPE_NAMES[type] || String(type);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function formatRelative(ts?: { seconds: bigint; nanos: number }): string {
|
|
159
|
+
if (!ts) return "";
|
|
160
|
+
const date = Timestamp.toDate(ts);
|
|
161
|
+
const diff = Date.now() - date.getTime();
|
|
162
|
+
const mins = Math.floor(diff / 60_000);
|
|
163
|
+
if (mins < 1) return "just now";
|
|
164
|
+
if (mins < 60) return `${mins}m ago`;
|
|
165
|
+
const hours = Math.floor(mins / 60);
|
|
166
|
+
if (hours < 24) return `${hours}h ago`;
|
|
167
|
+
const days = Math.floor(hours / 24);
|
|
168
|
+
return `${days}d ago`;
|
|
169
|
+
}
|
|
170
|
+
</script>
|