@frigear-nu/account 0.0.8 → 0.0.10

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 CHANGED
@@ -75,11 +75,7 @@ await changePassword({ currentPassword: 'old', newPassword: 'new-password', conf
75
75
  Strings live under the `account.*` namespace in `da`/`en` locale files and can be overridden in your app's
76
76
  `i18n/locales/<code>.json`. See the [documentation](https://docs.frigear.dev/modules/account#translations).
77
77
 
78
- ## Frigear ID (QR code)
79
-
80
- `settings.pages.idCode` (`true` or a path, default `/account/id`) adds a page with a QR code (`urn:frigear:id:<jwt>`)
81
- that refreshes every 60 seconds, plus an account menu tile. `GET /api/account/id-code` returns the RS256-signed JWT
82
- (60s lifetime, `sub` = user id). It signs with `runtimeConfig.account.idCode.privateKey`
83
- (`NUXT_ACCOUNT_ID_CODE_PRIVATE_KEY`) or, when unset, the `@frigear-nu/oauth-server` key (`runtimeConfig.oauthServer`),
84
- so scanners can verify it against `/.well-known/jwks.json`. Without a key the endpoint responds `501`.
85
- `AccountIdentificationCodeDialog` wraps the same content in a modal.
78
+ ## Overriding parts
79
+
80
+ Any `Account*` component can be replaced by a same-named component in your app (the module's have a lower priority).
81
+ `AccountHeaderRight` is the block at the right of the account header; override it to show other details.
package/dist/module.d.mts CHANGED
@@ -20,7 +20,6 @@ type PublicModuleOptions = {
20
20
  profile?: string;
21
21
  security?: string;
22
22
  documents?: string;
23
- idCode?: string;
24
23
  apiTokens?: string;
25
24
  };
26
25
  documents: AccountDocument[];
@@ -32,7 +31,6 @@ type ModuleOptions = {
32
31
  profile?: string | boolean;
33
32
  security?: string | boolean;
34
33
  documents?: string | boolean;
35
- idCode?: string | boolean;
36
34
  apiTokens?: string | boolean;
37
35
  };
38
36
  documents?: AccountDocument[];
@@ -43,15 +41,6 @@ declare module 'nuxt/schema' {
43
41
  interface NuxtConfig {
44
42
  account?: ModuleOptions;
45
43
  }
46
- interface RuntimeConfig {
47
- account?: {
48
- idCode?: {
49
- privateKey?: string;
50
- keyId?: string;
51
- issuer?: string;
52
- };
53
- };
54
- }
55
44
  interface PublicRuntimeConfig {
56
45
  account?: PublicModuleOptions;
57
46
  }
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@frigear-nu/account",
3
3
  "configKey": "account",
4
- "version": "0.0.8",
4
+ "version": "0.0.10",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule, createResolver, hasNuxtModule, addServerHandler, addImports, extendPages, addComponentsDir } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, hasNuxtModule, addImports, extendPages, addComponentsDir } from '@nuxt/kit';
2
2
  import { registerModule, registerTailwindSource, registerLocales, resolvePagePath, addOptionalMenuSource } from '@frigear-nu/module-kit/utils';
3
3
 
4
4
  const module$1 = defineNuxtModule({
@@ -30,30 +30,20 @@ const module$1 = defineNuxtModule({
30
30
  registerLocales(nuxt, resolver.resolve("./runtime/i18n/lang"));
31
31
  const settings = options.settings ?? {};
32
32
  const withMenus = hasNuxtModule("@frigear-nu/menu");
33
- addServerHandler({
34
- route: "/api/account/id-code",
35
- method: "get",
36
- handler: resolver.resolve("./runtime/server/routes/account/id-code.get")
37
- });
38
- const existingAccount = nuxt.options.runtimeConfig.account;
39
- nuxt.options.runtimeConfig.account = {
40
- idCode: { privateKey: "", keyId: "", issuer: "", ...existingAccount?.idCode }
41
- };
42
33
  addImports([{ name: "useAccount", from: resolver.resolve("./runtime/app/composables/useAccount") }]);
43
34
  const profilePath = resolvePagePath(settings.pages?.profile, "/account/profile");
44
35
  const securityPath = resolvePagePath(settings.pages?.security, "/account/security");
45
36
  const documentsPath = resolvePagePath(settings.pages?.documents, "/account/documents");
46
- const idCodePath = resolvePagePath(settings.pages?.idCode, "/account/id");
47
37
  const apiTokensPath = resolvePagePath(settings.pages?.apiTokens, "/account/api-tokens");
48
38
  const indexPath = withMenus ? resolvePagePath(settings.pages?.index, "/account") : void 0;
49
39
  if (!withMenus && settings.pages?.index) {
50
40
  console.warn("[@frigear-nu/account] `settings.pages.index` was set, but `@frigear-nu/menu` is not installed - skipping the dashboard page.");
51
41
  }
52
42
  nuxt.options.runtimeConfig.public.account = {
53
- pages: { index: indexPath, profile: profilePath, security: securityPath, documents: documentsPath, idCode: idCodePath, apiTokens: apiTokensPath },
43
+ pages: { index: indexPath, profile: profilePath, security: securityPath, documents: documentsPath, apiTokens: apiTokensPath },
54
44
  documents: settings.documents ?? []
55
45
  };
56
- if (indexPath || profilePath || securityPath || documentsPath || idCodePath || apiTokensPath) {
46
+ if (indexPath || profilePath || securityPath || documentsPath || apiTokensPath) {
57
47
  extendPages((pages) => {
58
48
  const existingPaths = new Set(pages.map((page) => page.path));
59
49
  const children = [];
@@ -64,7 +54,6 @@ const module$1 = defineNuxtModule({
64
54
  add(profilePath, "account-profile", "./runtime/app/pages/profile.vue");
65
55
  add(documentsPath, "account-documents", "./runtime/app/pages/documents.vue");
66
56
  add(securityPath, "account-security", "./runtime/app/pages/security.vue");
67
- add(idCodePath, "account-id-code", "./runtime/app/pages/id-code.vue");
68
57
  add(apiTokensPath, "account-api-tokens", "./runtime/app/pages/api-tokens.vue");
69
58
  if (children.length) {
70
59
  pages.push({ name: "account-layout", path: "/__account", file: resolver.resolve("./runtime/app/pages/layout.vue"), children });
@@ -74,7 +63,7 @@ const module$1 = defineNuxtModule({
74
63
  if (hasNuxtModule("@frigear-nu/authorization")) {
75
64
  const prefixes = ["", ...(nuxt.options.i18n?.locales ?? []).map((locale) => typeof locale === "string" ? locale : locale.code).map((code) => `/${code}`)];
76
65
  const rules = nuxt.options.routeRules ||= {};
77
- const guarded = [indexPath, profilePath, securityPath, documentsPath, idCodePath, apiTokensPath].filter((path) => !!path && !path.startsWith("/account"));
66
+ const guarded = [indexPath, profilePath, securityPath, documentsPath, apiTokensPath].filter((path) => !!path && !path.startsWith("/account"));
78
67
  for (const prefix of prefixes) {
79
68
  for (const pattern of [`${prefix}/account`, `${prefix}/account/**`, ...guarded.map((path) => `${prefix}${path}`)]) {
80
69
  rules[pattern] = { authenticated: true, ...rules[pattern] };
@@ -85,7 +74,8 @@ const module$1 = defineNuxtModule({
85
74
  }
86
75
  addOptionalMenuSource(nuxt, { file: resolver.resolve("./runtime/menus"), menu: "dashboard.account" });
87
76
  addComponentsDir({
88
- path: resolver.resolve("./runtime/app/components")
77
+ path: resolver.resolve("./runtime/app/components"),
78
+ priority: -1
89
79
  });
90
80
  }
91
81
  });
@@ -1,7 +1,6 @@
1
1
  <script setup>
2
2
  const t = useModuleT("account");
3
3
  const route = useRoute();
4
- const { user } = useUserSession();
5
4
  const pages = useRuntimeConfig().public.account?.pages;
6
5
  const nuxtApp = useNuxtApp();
7
6
  const items = typeof useMenu === "function" ? useMenu("account").data : ref([]);
@@ -37,22 +36,7 @@ const description = computed(() => current.value?.description);
37
36
  />
38
37
 
39
38
  <template #links>
40
- <div
41
- v-if="user"
42
- class="flex flex-col"
43
- >
44
- <div class="text-sm text-muted">
45
- {{ t("signedInAs", "Signed in as") }}
46
- </div>
47
- <div class="text-primary">
48
- {{ user.email }}
49
- </div>
50
- <div class="mt-1 flex justify-end">
51
- <UBadge variant="soft">
52
- Frigear ID: <code>{{ user.id }}</code>
53
- </UBadge>
54
- </div>
55
- </div>
39
+ <AccountHeaderRight />
56
40
  </template>
57
41
  </UPageHeader>
58
42
  </UContainer>
@@ -0,0 +1,18 @@
1
+ <script setup>
2
+ const t = useModuleT("account");
3
+ const { user } = useUserSession();
4
+ </script>
5
+
6
+ <template>
7
+ <div
8
+ v-if="user"
9
+ class="flex flex-col"
10
+ >
11
+ <div class="text-sm text-muted">
12
+ {{ t("signedInAs", "Signed in as") }}
13
+ </div>
14
+ <div class="text-primary">
15
+ {{ user.email }}
16
+ </div>
17
+ </div>
18
+ </template>
@@ -24,14 +24,6 @@
24
24
  "description": "Opret nøgler til scripts og værktøjer."
25
25
  }
26
26
  },
27
- "identificationCode": {
28
- "title": "Frigear ID",
29
- "description": "Tryk her for at vise dit ID.",
30
- "expiresIn": "Fornyes om {time}s",
31
- "refresh": "Opdater",
32
- "role": "Rolle",
33
- "error": "Kunne ikke oprette dit Frigear ID."
34
- },
35
27
  "profile": {
36
28
  "title": "Profil",
37
29
  "description": "Skift dine brugeroplysninger.",
@@ -24,14 +24,6 @@
24
24
  "description": "Create tokens for scripts and tools."
25
25
  }
26
26
  },
27
- "identificationCode": {
28
- "title": "Frigear ID",
29
- "description": "Press here to show your ID.",
30
- "expiresIn": "Refreshes in {time}s",
31
- "refresh": "Refresh",
32
- "role": "Role",
33
- "error": "Could not create your Frigear ID."
34
- },
35
27
  "profile": {
36
28
  "title": "Profile",
37
29
  "description": "Change your information.",
@@ -63,16 +63,4 @@ export default defineMenuSource(() => {
63
63
  order: 30
64
64
  });
65
65
  }
66
- if (config?.pages?.idCode) {
67
- extendMenuItem("account", {
68
- id: "id-code",
69
- label: "Frigear ID",
70
- labelKey: "account.identificationCode.title",
71
- description: "Press here to show your ID.",
72
- descriptionKey: "account.identificationCode.description",
73
- icon: "i-lucide-qr-code",
74
- to: config.pages.idCode,
75
- order: 5
76
- });
77
- }
78
66
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frigear-nu/account",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "A module for managing the account area (profile, email, password, security) in the main frigear app.",
5
5
  "homepage": "https://docs.frigear.dev/modules/account",
6
6
  "repository": {
@@ -24,21 +24,19 @@
24
24
  "playground"
25
25
  ],
26
26
  "dependencies": {
27
- "@frigear-nu/module-kit": "0.1.7",
28
- "@frigear-nu/auth": "0.0.3",
29
- "@frigear-nu/users": "1.0.9",
27
+ "@frigear-nu/module-kit": "1.0.1",
28
+ "@frigear-nu/auth": "0.0.5",
29
+ "@frigear-nu/users": "1.0.11",
30
30
  "@libsql/client": "^0.18.0",
31
31
  "@nuxt/kit": "^4.6.0",
32
32
  "@vueuse/core": "^15.0.0",
33
33
  "drizzle-kit": "^0.31.11",
34
34
  "drizzle-orm": "^0.45.3",
35
- "jose": "^6.1.0",
36
- "uqr": "^0.1.2",
37
35
  "zod": "^4.6.5"
38
36
  },
39
37
  "peerDependencies": {
40
- "@frigear-nu/authorization": "0.0.6",
41
- "@frigear-nu/menu": "0.0.9",
38
+ "@frigear-nu/authorization": "0.0.8",
39
+ "@frigear-nu/menu": "0.0.11",
42
40
  "@nuxt/ui": "^4.10.0",
43
41
  "@nuxthub/core": "^0.10.8"
44
42
  },
@@ -54,8 +52,8 @@
54
52
  }
55
53
  },
56
54
  "devDependencies": {
57
- "@frigear-nu/authorization": "0.0.6",
58
- "@frigear-nu/menu": "0.0.9",
55
+ "@frigear-nu/authorization": "0.0.8",
56
+ "@frigear-nu/menu": "0.0.11",
59
57
  "@nuxt/devtools": "^3.4.2",
60
58
  "@nuxt/eslint-config": "^1.17.0",
61
59
  "@nuxt/module-builder": "^1.0.3",
@@ -1,82 +0,0 @@
1
- <script setup>
2
- import { renderSVG } from "uqr";
3
- import { useIntervalFn } from "@vueuse/core";
4
- const t = useModuleT("account.identificationCode");
5
- const { user } = useUserSession();
6
- const { data, refresh, status, error } = useFetch("/api/account/id-code", { server: false });
7
- const remaining = ref(0);
8
- watch(data, (value) => {
9
- remaining.value = value?.expiresIn ?? 0;
10
- }, { immediate: true });
11
- const refreshing = computed(() => status.value === "pending");
12
- useIntervalFn(() => {
13
- if (!data.value || refreshing.value) return;
14
- if (remaining.value <= 1) {
15
- remaining.value = 0;
16
- refresh();
17
- return;
18
- }
19
- remaining.value -= 1;
20
- }, 1e3);
21
- const svg = computed(() => data.value ? renderSVG(data.value.payload, { ecc: "L", border: 1 }) : "");
22
- const role = computed(() => user.value?.role);
23
- const membership = computed(() => user.value?.membership);
24
- </script>
25
-
26
- <template>
27
- <div class="flex flex-col items-center gap-3">
28
- <UAlert
29
- v-if="error"
30
- color="error"
31
- variant="subtle"
32
- icon="i-lucide-triangle-alert"
33
- :title="t('error', 'Could not create your Frigear ID.')"
34
- />
35
- <template v-else-if="svg">
36
- <!-- eslint-disable-next-line vue/no-v-html -->
37
- <div
38
- class="size-60 rounded-md bg-white p-2 [&>svg]:size-full"
39
- v-html="svg"
40
- />
41
- <UBadge
42
- color="warning"
43
- class="text-xl font-bold"
44
- >
45
- {{ t("expiresIn", "Refreshes in {time}s").replace("{time}", String(remaining)) }}
46
- </UBadge>
47
- <div class="flex flex-wrap justify-center gap-2">
48
- <UBadge
49
- v-if="user"
50
- size="lg"
51
- >
52
- Frigear ID: {{ user.id }}
53
- </UBadge>
54
- <UBadge
55
- v-if="membership"
56
- size="lg"
57
- >
58
- {{ membership }}
59
- </UBadge>
60
- <UBadge
61
- v-if="role"
62
- size="lg"
63
- color="success"
64
- >
65
- {{ t("role", "Role") }}: {{ role }}
66
- </UBadge>
67
- </div>
68
- </template>
69
- <UIcon
70
- v-else
71
- name="i-lucide-refresh-cw"
72
- class="size-6 animate-spin"
73
- />
74
- <UButton
75
- icon="i-lucide-refresh-cw"
76
- variant="subtle"
77
- :loading="refreshing"
78
- :label="t('refresh', 'Refresh')"
79
- @click="refresh()"
80
- />
81
- </div>
82
- </template>
@@ -1,20 +0,0 @@
1
- type __VLS_ModelProps = {
2
- 'open'?: boolean;
3
- };
4
- declare var __VLS_8: {};
5
- type __VLS_Slots = {} & {
6
- default?: (props: typeof __VLS_8) => any;
7
- };
8
- declare const __VLS_base: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
- "update:open": (value: boolean) => any;
10
- }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
11
- "onUpdate:open"?: ((value: boolean) => any) | undefined;
12
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
- declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
14
- declare const _default: typeof __VLS_export;
15
- export default _default;
16
- type __VLS_WithSlots<T, S> = T & {
17
- new (): {
18
- $slots: S;
19
- };
20
- };
@@ -1,16 +0,0 @@
1
- <script setup>
2
- const t = useModuleT("account.identificationCode");
3
- const open = defineModel("open", { type: Boolean, ...{ default: false } });
4
- </script>
5
-
6
- <template>
7
- <UModal
8
- v-model:open="open"
9
- :title="t('title', 'Frigear ID')"
10
- >
11
- <slot />
12
- <template #body>
13
- <AccountIdentificationCode v-if="open" />
14
- </template>
15
- </UModal>
16
- </template>
@@ -1,20 +0,0 @@
1
- type __VLS_ModelProps = {
2
- 'open'?: boolean;
3
- };
4
- declare var __VLS_8: {};
5
- type __VLS_Slots = {} & {
6
- default?: (props: typeof __VLS_8) => any;
7
- };
8
- declare const __VLS_base: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
- "update:open": (value: boolean) => any;
10
- }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
11
- "onUpdate:open"?: ((value: boolean) => any) | undefined;
12
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
- declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
14
- declare const _default: typeof __VLS_export;
15
- export default _default;
16
- type __VLS_WithSlots<T, S> = T & {
17
- new (): {
18
- $slots: S;
19
- };
20
- };
@@ -1,3 +0,0 @@
1
- declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
- declare const _default: typeof __VLS_export;
3
- export default _default;
@@ -1,13 +0,0 @@
1
- <script setup>
2
- defineOptions({ name: "AccountIdCodePage" });
3
- </script>
4
-
5
- <template>
6
- <UContainer>
7
- <UPage>
8
- <UPageBody>
9
- <AccountIdentificationCode />
10
- </UPageBody>
11
- </UPage>
12
- </UContainer>
13
- </template>
@@ -1,3 +0,0 @@
1
- declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
- declare const _default: typeof __VLS_export;
3
- export default _default;
@@ -1,2 +0,0 @@
1
- declare const _default: any;
2
- export default _default;
@@ -1,20 +0,0 @@
1
- import { SignJWT, importPKCS8 } from "jose";
2
- const TTL_SECONDS = 60;
3
- function normalizePem(raw) {
4
- return raw.trim().replace(/^["']|["']$/g, "").replace(/\\n/g, "\n").trim();
5
- }
6
- export default defineEventHandler(async (event) => {
7
- const user = await requireUser(event, { sessionOnly: true });
8
- const config = useRuntimeConfig(event);
9
- const own = config.account?.idCode;
10
- const oauth = config.oauthServer;
11
- const pem = normalizePem(own?.privateKey || oauth?.privateKey || "");
12
- if (!pem) {
13
- throw createError({ statusCode: 501, statusMessage: "Frigear ID is not configured: set a signing key." });
14
- }
15
- const keyId = own?.keyId || oauth?.keyId || "key-1";
16
- const issuer = own?.issuer || oauth?.issuer || getRequestURL(event).origin;
17
- const key = await importPKCS8(pem, "RS256");
18
- const jwt = await new SignJWT({ typ: "frigear:id" }).setProtectedHeader({ alg: "RS256", kid: keyId }).setIssuer(issuer).setSubject(String(user.id)).setAudience("frigear:id").setIssuedAt().setExpirationTime(`${TTL_SECONDS}s`).setJti(crypto.randomUUID()).sign(key);
19
- return { idCode: jwt, payload: `urn:frigear:id:${jwt}`, expiresIn: TTL_SECONDS };
20
- });