@ricardoqmd/auth-vue 0.1.0 → 0.2.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 CHANGED
@@ -5,9 +5,13 @@
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install @ricardoqmd/auth-core @ricardoqmd/auth-keycloak @ricardoqmd/auth-vue vue
8
+ npm install @ricardoqmd/auth-core @ricardoqmd/auth-keycloak @ricardoqmd/auth-vue vue keycloak-js xstate
9
9
  ```
10
10
 
11
+ > Install `xstate` even though you never import it: `@ricardoqmd/auth-core` and
12
+ > `@xstate/vue` declare it as a peer, and a single shared instance must resolve at
13
+ > the top level (a duplicate copy would break the actor's reactivity).
14
+
11
15
  > Scope is SPA / client-only (ADR-012). The plugin eagerly initializes the auth
12
16
  > flow on install, which assumes a browser. It is SSR-ready by construction (one
13
17
  > actor per app instance, never a module-level singleton) but SSR is not a
@@ -30,9 +34,14 @@ export const provider = createKeycloakProvider({
30
34
  realm: import.meta.env.VITE_KC_REALM,
31
35
  clientId: import.meta.env.VITE_KC_CLIENT_ID,
32
36
  },
37
+ onLoad: "check-sso",
33
38
  });
34
39
  ```
35
40
 
41
+ > `check-sso` boots the app without forcing a login — anonymous users land on
42
+ > your UI and sign in on demand. Use `login-required` instead to redirect
43
+ > straight to Keycloak before the app renders.
44
+
36
45
  ### 2. Install the plugin
37
46
 
38
47
  ```ts
@@ -118,12 +127,54 @@ const { isLoading, error, isAuthenticated } = useAuth();
118
127
  </template>
119
128
  ```
120
129
 
130
+ ### Route guards (imperative)
131
+
132
+ `useAuth()` is for components — it is reactive and **throws outside `setup()`**.
133
+ For code that runs outside the render tree (a vue-router `beforeEach` guard, an
134
+ HTTP interceptor), use the value returned by `createAuth(...)`: it is BOTH a Vue
135
+ plugin AND an imperative `AuthHandle`. The same object you install with
136
+ `app.use()` exposes synchronous accessors.
137
+
138
+ ```ts
139
+ // main.ts
140
+ import { createApp } from "vue";
141
+ import { createAuth } from "@ricardoqmd/auth-vue";
142
+ import { router } from "./router";
143
+ import App from "./App.vue";
144
+ import { provider } from "./auth";
145
+
146
+ const auth = createAuth({ provider });
147
+ const app = createApp(App);
148
+ app.use(auth);
149
+ app.use(router);
150
+
151
+ router.beforeEach(async (to) => {
152
+ await auth.whenReady(); // wait out the first-navigation init race
153
+ if (to.meta.requiresAuth && !auth.isAuthenticated()) return { name: "login" };
154
+ if (to.meta.roles && !auth.hasAnyRole(to.meta.roles as string[])) {
155
+ return { name: "forbidden" };
156
+ }
157
+ });
158
+
159
+ app.mount("#app");
160
+ ```
161
+
162
+ `auth.whenReady()` resolves once `init()` settles (authenticated, unauthenticated,
163
+ or error), so the first navigation does not race a pending initialization. The
164
+ handle also exposes `isAuthenticated()`, `isLoading()`, `getToken()`, `getUser()`,
165
+ `getIdpClaims()`, `getError()`, `hasRole()`, `hasAnyRole()`, and `subscribe()`.
166
+ Use `useAuth()` in components (reactive); use the handle in guards/interceptors
167
+ (imperative).
168
+
121
169
  ## API
122
170
 
123
171
  ### `createAuth(options)`
124
172
 
125
- Returns a Vue `Plugin`. Install with `app.use(createAuth({ provider }))`. Creates
126
- one auth actor per app instance, starts it, sends `INIT`, and provides it app-wide.
173
+ Returns a value that is BOTH a Vue `Plugin` AND an `AuthHandle<TIdpClaims>`.
174
+ Install it with `app.use(createAuth({ provider }))`; the same object is usable
175
+ imperatively outside components (see [Route guards](#route-guards-imperative)).
176
+ Creates one auth actor per call, starts it, sends `INIT`, and provides it
177
+ app-wide.
127
178
 
128
179
  | Option | Type | Description |
129
180
  |---|---|---|
@@ -161,6 +212,10 @@ currently emitted by `auth-keycloak` — a dead refresh token rejects as
161
212
  **Pre-1.0.** The public API mirrors `@ricardoqmd/auth-nextjs` and shares the
162
213
  `@ricardoqmd/auth-core` contract.
163
214
 
215
+ `@ricardoqmd/auth-vue` is `0.x` and versions **independently** from
216
+ `@ricardoqmd/auth-core` and `@ricardoqmd/auth-keycloak` (ADR-013): its version
217
+ does not track theirs, and the API is not frozen until `1.0`.
218
+
164
219
  ## License
165
220
 
166
221
  MIT © [ricardoqmd](https://github.com/ricardoqmd)
package/dist/index.cjs CHANGED
@@ -12,12 +12,14 @@ var AUTH_INJECTION_KEY = /* @__PURE__ */ Symbol("ricardoqmd-auth");
12
12
 
13
13
  // src/plugin.ts
14
14
  function createAuth(options) {
15
+ const machine = authCore.createAuthMachine(options.provider);
16
+ const actor = xstate.createActor(machine);
17
+ actor.start();
18
+ actor.send({ type: "INIT" });
19
+ const handle = authCore.createAuthHandle(actor);
15
20
  return {
21
+ ...handle,
16
22
  install(app) {
17
- const machine = authCore.createAuthMachine(options.provider);
18
- const actor = xstate.createActor(machine);
19
- actor.start();
20
- actor.send({ type: "INIT" });
21
23
  app.provide(AUTH_INJECTION_KEY, actor);
22
24
  const unmountable = app;
23
25
  if (typeof unmountable.onUnmount === "function") {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/injection-key.ts","../src/plugin.ts","../src/composable.ts"],"names":["createAuthMachine","createActor","inject","useSelector","computed"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { InjectionKey } from \"vue\";\nimport type { Actor } from \"xstate\";\nimport type { createAuthMachine } from \"@ricardoqmd/auth-core\";\n\n/** The started auth actor type, derived from the core machine factory. */\nexport type AuthActor<TIdpClaims = unknown> = Actor<\n ReturnType<typeof createAuthMachine<TIdpClaims>>\n>;\n\n/** App-wide injection key for the auth actor created by the createAuth plugin. */\nexport const AUTH_INJECTION_KEY: InjectionKey<AuthActor> = Symbol(\"ricardoqmd-auth\");\n","import type { App, Plugin } from \"vue\";\nimport { createActor } from \"xstate\";\nimport { createAuthMachine, type AuthProvider } from \"@ricardoqmd/auth-core\";\nimport { AUTH_INJECTION_KEY, type AuthActor } from \"./injection-key.js\";\n\nexport interface CreateAuthOptions<TIdpClaims = unknown> {\n /** Adapter instance (e.g. createKeycloakProvider()). IDP-agnostic. */\n provider: AuthProvider<TIdpClaims>;\n}\n\n/**\n * Vue plugin wiring @ricardoqmd/auth-core into a Vue app.\n *\n * Creates ONE auth actor per app instance (SSR-safe: never a module-level\n * singleton), starts it, kicks off initialization, and provides it app-wide\n * for useAuth(). Scope is SPA/client-only (ADR-012): the eager INIT below\n * assumes a browser. SSR-ready by construction (per-app instance) but not\n * SSR-supported.\n */\nexport function createAuth<TIdpClaims = unknown>(\n options: CreateAuthOptions<TIdpClaims>,\n): Plugin {\n return {\n install(app: App) {\n const machine = createAuthMachine<TIdpClaims>(options.provider);\n const actor = createActor(machine);\n actor.start();\n actor.send({ type: \"INIT\" });\n app.provide(AUTH_INJECTION_KEY, actor as AuthActor);\n // Stop the actor when the app unmounts (avoids leaks in tests/HMR).\n // app.onUnmount requires Vue 3.5+; guard so older runtimes still install.\n const unmountable = app as App & { onUnmount?: (cb: () => void) => void };\n if (typeof unmountable.onUnmount === \"function\") {\n unmountable.onUnmount(() => actor.stop());\n }\n },\n };\n}\n","import { computed, inject, type ComputedRef } from \"vue\";\nimport { useSelector } from \"@xstate/vue\";\nimport type { AuthError, AuthUserClaims } from \"@ricardoqmd/auth-core\";\nimport { AUTH_INJECTION_KEY } from \"./injection-key.js\";\n\n/** Reactive auth state + helpers returned by useAuth(). */\nexport interface AuthState<TIdpClaims = unknown> {\n isLoading: ComputedRef<boolean>;\n isAuthenticated: ComputedRef<boolean>;\n token: ComputedRef<string | null>;\n user: ComputedRef<AuthUserClaims | null>;\n idpClaims: ComputedRef<TIdpClaims | null>;\n error: ComputedRef<AuthError | null>;\n login: () => void;\n logout: () => void;\n hasRole: (role: string) => boolean;\n hasAnyRole: (roles: string[]) => boolean;\n}\n\n/**\n * Reactive authentication state for the current app.\n * Must be called inside a component of an app that installed createAuth().\n */\nexport function useAuth<TIdpClaims = unknown>(): AuthState<TIdpClaims> {\n const actor = inject(AUTH_INJECTION_KEY);\n if (!actor) {\n throw new Error(\n \"useAuth() must be called within an app that installed the auth plugin. \" +\n \"Did you forget app.use(createAuth({ provider }))?\",\n );\n }\n\n const stateValue = useSelector(actor, (s) => s.value);\n const context = useSelector(actor, (s) => s.context);\n\n const isAuthenticated = computed(\n () =>\n typeof stateValue.value === \"object\" &&\n stateValue.value !== null &&\n \"authenticated\" in stateValue.value,\n );\n\n const topState = computed(() =>\n typeof stateValue.value === \"string\"\n ? stateValue.value\n : Object.keys(stateValue.value)[0],\n );\n\n const isLoading = computed(\n () => topState.value === \"initializing\" || topState.value === \"loggingOut\",\n );\n\n const token = computed(() => context.value.token);\n const user = computed(() => context.value.user);\n const idpClaims = computed(() => context.value.idpClaims as TIdpClaims | null);\n const error = computed(() => context.value.error);\n\n const login = () => actor.send({ type: \"LOGIN\" });\n const logout = () => actor.send({ type: \"LOGOUT\" });\n\n const hasRole = (role: string) =>\n user.value?.roles?.includes(role) ?? false;\n const hasAnyRole = (roles: string[]) =>\n roles.some((r) => user.value?.roles?.includes(r) ?? false);\n\n return {\n isLoading,\n isAuthenticated,\n token,\n user,\n idpClaims,\n error,\n login,\n logout,\n hasRole,\n hasAnyRole,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/injection-key.ts","../src/plugin.ts","../src/composable.ts"],"names":["createAuthMachine","createActor","createAuthHandle","inject","useSelector","computed"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { InjectionKey } from \"vue\";\nimport type { Actor } from \"xstate\";\nimport type { createAuthMachine } from \"@ricardoqmd/auth-core\";\n\n/** The started auth actor type, derived from the core machine factory. */\nexport type AuthActor<TIdpClaims = unknown> = Actor<\n ReturnType<typeof createAuthMachine<TIdpClaims>>\n>;\n\n/** App-wide injection key for the auth actor created by the createAuth plugin. */\nexport const AUTH_INJECTION_KEY: InjectionKey<AuthActor> = Symbol(\"ricardoqmd-auth\");\n","import type { App, Plugin } from \"vue\";\nimport { createActor } from \"xstate\";\nimport {\n createAuthHandle,\n createAuthMachine,\n type AuthHandle,\n type AuthProvider,\n} from \"@ricardoqmd/auth-core\";\nimport { AUTH_INJECTION_KEY, type AuthActor } from \"./injection-key.js\";\n\nexport interface CreateAuthOptions<TIdpClaims = unknown> {\n /** Adapter instance (e.g. createKeycloakProvider()). IDP-agnostic. */\n provider: AuthProvider<TIdpClaims>;\n}\n\n/**\n * Wire @ricardoqmd/auth-core into a Vue app.\n *\n * Returns a value that is BOTH a Vue plugin and an imperative AuthHandle:\n * - `app.use(auth)` installs it (provides the actor for useAuth()).\n * - the same `auth` object exposes whenReady()/isAuthenticated()/hasRole()/... for\n * use OUTSIDE components — a vue-router `beforeEach` guard or an HTTP interceptor,\n * where useAuth() cannot run.\n *\n * Creates ONE actor per call (SSR-safe: never a module-level singleton). The actor\n * is started and initialized here, so the handle is usable before mount (guards run\n * before the app renders). Scope is SPA/client-only (ADR-012): the eager INIT\n * assumes a browser; SSR-ready by construction but not a supported target in 0.x.\n *\n * @example\n * const auth = createAuth({ provider });\n * app.use(auth);\n * router.beforeEach(async (to) => {\n * await auth.whenReady();\n * if (to.meta.requiresAuth && !auth.isAuthenticated()) return { name: \"login\" };\n * if (to.meta.roles && !auth.hasAnyRole(to.meta.roles as string[])) {\n * return { name: \"forbidden\" };\n * }\n * });\n */\nexport function createAuth<TIdpClaims = unknown>(\n options: CreateAuthOptions<TIdpClaims>,\n): Plugin & AuthHandle<TIdpClaims> {\n const machine = createAuthMachine<TIdpClaims>(options.provider);\n const actor = createActor(machine);\n actor.start();\n actor.send({ type: \"INIT\" });\n const handle = createAuthHandle<TIdpClaims>(actor);\n\n return {\n ...handle,\n install(app: App) {\n app.provide(AUTH_INJECTION_KEY, actor as AuthActor);\n // Stop the actor when the app unmounts (avoids leaks in tests/HMR).\n // app.onUnmount requires Vue 3.5+; guard so older runtimes still install.\n const unmountable = app as App & { onUnmount?: (cb: () => void) => void };\n if (typeof unmountable.onUnmount === \"function\") {\n unmountable.onUnmount(() => actor.stop());\n }\n },\n };\n}\n","import { computed, inject, type ComputedRef } from \"vue\";\nimport { useSelector } from \"@xstate/vue\";\nimport type { AuthError, AuthUserClaims } from \"@ricardoqmd/auth-core\";\nimport { AUTH_INJECTION_KEY } from \"./injection-key.js\";\n\n/** Reactive auth state + helpers returned by useAuth(). */\nexport interface AuthState<TIdpClaims = unknown> {\n isLoading: ComputedRef<boolean>;\n isAuthenticated: ComputedRef<boolean>;\n token: ComputedRef<string | null>;\n user: ComputedRef<AuthUserClaims | null>;\n idpClaims: ComputedRef<TIdpClaims | null>;\n error: ComputedRef<AuthError | null>;\n login: () => void;\n logout: () => void;\n hasRole: (role: string) => boolean;\n hasAnyRole: (roles: string[]) => boolean;\n}\n\n/**\n * Reactive authentication state for the current app.\n * Must be called inside a component of an app that installed createAuth().\n */\nexport function useAuth<TIdpClaims = unknown>(): AuthState<TIdpClaims> {\n const actor = inject(AUTH_INJECTION_KEY);\n if (!actor) {\n throw new Error(\n \"useAuth() must be called within an app that installed the auth plugin. \" +\n \"Did you forget app.use(createAuth({ provider }))?\",\n );\n }\n\n const stateValue = useSelector(actor, (s) => s.value);\n const context = useSelector(actor, (s) => s.context);\n\n const isAuthenticated = computed(\n () =>\n typeof stateValue.value === \"object\" &&\n stateValue.value !== null &&\n \"authenticated\" in stateValue.value,\n );\n\n const topState = computed(() =>\n typeof stateValue.value === \"string\"\n ? stateValue.value\n : Object.keys(stateValue.value)[0],\n );\n\n const isLoading = computed(\n () => topState.value === \"initializing\" || topState.value === \"loggingOut\",\n );\n\n const token = computed(() => context.value.token);\n const user = computed(() => context.value.user);\n const idpClaims = computed(() => context.value.idpClaims as TIdpClaims | null);\n const error = computed(() => context.value.error);\n\n const login = () => actor.send({ type: \"LOGIN\" });\n const logout = () => actor.send({ type: \"LOGOUT\" });\n\n const hasRole = (role: string) =>\n user.value?.roles?.includes(role) ?? false;\n const hasAnyRole = (roles: string[]) =>\n roles.some((r) => user.value?.roles?.includes(r) ?? false);\n\n return {\n isLoading,\n isAuthenticated,\n token,\n user,\n idpClaims,\n error,\n login,\n logout,\n hasRole,\n hasAnyRole,\n };\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Plugin, ComputedRef } from 'vue';
2
- import { AuthProvider, AuthUserClaims, AuthError, createAuthMachine } from '@ricardoqmd/auth-core';
3
- export { AuthError, AuthInitResult, AuthProvider, AuthTokens, AuthUserClaims, LogoutOptions } from '@ricardoqmd/auth-core';
2
+ import { AuthProvider, AuthHandle, AuthUserClaims, AuthError, createAuthMachine } from '@ricardoqmd/auth-core';
3
+ export { AuthError, AuthHandle, AuthInitResult, AuthProvider, AuthTokens, AuthUserClaims, LogoutOptions } from '@ricardoqmd/auth-core';
4
4
  import { Actor } from 'xstate';
5
5
 
6
6
  interface CreateAuthOptions<TIdpClaims = unknown> {
@@ -8,15 +8,31 @@ interface CreateAuthOptions<TIdpClaims = unknown> {
8
8
  provider: AuthProvider<TIdpClaims>;
9
9
  }
10
10
  /**
11
- * Vue plugin wiring @ricardoqmd/auth-core into a Vue app.
11
+ * Wire @ricardoqmd/auth-core into a Vue app.
12
12
  *
13
- * Creates ONE auth actor per app instance (SSR-safe: never a module-level
14
- * singleton), starts it, kicks off initialization, and provides it app-wide
15
- * for useAuth(). Scope is SPA/client-only (ADR-012): the eager INIT below
16
- * assumes a browser. SSR-ready by construction (per-app instance) but not
17
- * SSR-supported.
13
+ * Returns a value that is BOTH a Vue plugin and an imperative AuthHandle:
14
+ * - `app.use(auth)` installs it (provides the actor for useAuth()).
15
+ * - the same `auth` object exposes whenReady()/isAuthenticated()/hasRole()/... for
16
+ * use OUTSIDE components — a vue-router `beforeEach` guard or an HTTP interceptor,
17
+ * where useAuth() cannot run.
18
+ *
19
+ * Creates ONE actor per call (SSR-safe: never a module-level singleton). The actor
20
+ * is started and initialized here, so the handle is usable before mount (guards run
21
+ * before the app renders). Scope is SPA/client-only (ADR-012): the eager INIT
22
+ * assumes a browser; SSR-ready by construction but not a supported target in 0.x.
23
+ *
24
+ * @example
25
+ * const auth = createAuth({ provider });
26
+ * app.use(auth);
27
+ * router.beforeEach(async (to) => {
28
+ * await auth.whenReady();
29
+ * if (to.meta.requiresAuth && !auth.isAuthenticated()) return { name: "login" };
30
+ * if (to.meta.roles && !auth.hasAnyRole(to.meta.roles as string[])) {
31
+ * return { name: "forbidden" };
32
+ * }
33
+ * });
18
34
  */
19
- declare function createAuth<TIdpClaims = unknown>(options: CreateAuthOptions<TIdpClaims>): Plugin;
35
+ declare function createAuth<TIdpClaims = unknown>(options: CreateAuthOptions<TIdpClaims>): Plugin & AuthHandle<TIdpClaims>;
20
36
 
21
37
  /** Reactive auth state + helpers returned by useAuth(). */
22
38
  interface AuthState<TIdpClaims = unknown> {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Plugin, ComputedRef } from 'vue';
2
- import { AuthProvider, AuthUserClaims, AuthError, createAuthMachine } from '@ricardoqmd/auth-core';
3
- export { AuthError, AuthInitResult, AuthProvider, AuthTokens, AuthUserClaims, LogoutOptions } from '@ricardoqmd/auth-core';
2
+ import { AuthProvider, AuthHandle, AuthUserClaims, AuthError, createAuthMachine } from '@ricardoqmd/auth-core';
3
+ export { AuthError, AuthHandle, AuthInitResult, AuthProvider, AuthTokens, AuthUserClaims, LogoutOptions } from '@ricardoqmd/auth-core';
4
4
  import { Actor } from 'xstate';
5
5
 
6
6
  interface CreateAuthOptions<TIdpClaims = unknown> {
@@ -8,15 +8,31 @@ interface CreateAuthOptions<TIdpClaims = unknown> {
8
8
  provider: AuthProvider<TIdpClaims>;
9
9
  }
10
10
  /**
11
- * Vue plugin wiring @ricardoqmd/auth-core into a Vue app.
11
+ * Wire @ricardoqmd/auth-core into a Vue app.
12
12
  *
13
- * Creates ONE auth actor per app instance (SSR-safe: never a module-level
14
- * singleton), starts it, kicks off initialization, and provides it app-wide
15
- * for useAuth(). Scope is SPA/client-only (ADR-012): the eager INIT below
16
- * assumes a browser. SSR-ready by construction (per-app instance) but not
17
- * SSR-supported.
13
+ * Returns a value that is BOTH a Vue plugin and an imperative AuthHandle:
14
+ * - `app.use(auth)` installs it (provides the actor for useAuth()).
15
+ * - the same `auth` object exposes whenReady()/isAuthenticated()/hasRole()/... for
16
+ * use OUTSIDE components — a vue-router `beforeEach` guard or an HTTP interceptor,
17
+ * where useAuth() cannot run.
18
+ *
19
+ * Creates ONE actor per call (SSR-safe: never a module-level singleton). The actor
20
+ * is started and initialized here, so the handle is usable before mount (guards run
21
+ * before the app renders). Scope is SPA/client-only (ADR-012): the eager INIT
22
+ * assumes a browser; SSR-ready by construction but not a supported target in 0.x.
23
+ *
24
+ * @example
25
+ * const auth = createAuth({ provider });
26
+ * app.use(auth);
27
+ * router.beforeEach(async (to) => {
28
+ * await auth.whenReady();
29
+ * if (to.meta.requiresAuth && !auth.isAuthenticated()) return { name: "login" };
30
+ * if (to.meta.roles && !auth.hasAnyRole(to.meta.roles as string[])) {
31
+ * return { name: "forbidden" };
32
+ * }
33
+ * });
18
34
  */
19
- declare function createAuth<TIdpClaims = unknown>(options: CreateAuthOptions<TIdpClaims>): Plugin;
35
+ declare function createAuth<TIdpClaims = unknown>(options: CreateAuthOptions<TIdpClaims>): Plugin & AuthHandle<TIdpClaims>;
20
36
 
21
37
  /** Reactive auth state + helpers returned by useAuth(). */
22
38
  interface AuthState<TIdpClaims = unknown> {
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createActor } from 'xstate';
2
- import { createAuthMachine } from '@ricardoqmd/auth-core';
2
+ import { createAuthMachine, createAuthHandle } from '@ricardoqmd/auth-core';
3
3
  import { inject, computed } from 'vue';
4
4
  import { useSelector } from '@xstate/vue';
5
5
 
@@ -10,12 +10,14 @@ var AUTH_INJECTION_KEY = /* @__PURE__ */ Symbol("ricardoqmd-auth");
10
10
 
11
11
  // src/plugin.ts
12
12
  function createAuth(options) {
13
+ const machine = createAuthMachine(options.provider);
14
+ const actor = createActor(machine);
15
+ actor.start();
16
+ actor.send({ type: "INIT" });
17
+ const handle = createAuthHandle(actor);
13
18
  return {
19
+ ...handle,
14
20
  install(app) {
15
- const machine = createAuthMachine(options.provider);
16
- const actor = createActor(machine);
17
- actor.start();
18
- actor.send({ type: "INIT" });
19
21
  app.provide(AUTH_INJECTION_KEY, actor);
20
22
  const unmountable = app;
21
23
  if (typeof unmountable.onUnmount === "function") {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/injection-key.ts","../src/plugin.ts","../src/composable.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { InjectionKey } from \"vue\";\nimport type { Actor } from \"xstate\";\nimport type { createAuthMachine } from \"@ricardoqmd/auth-core\";\n\n/** The started auth actor type, derived from the core machine factory. */\nexport type AuthActor<TIdpClaims = unknown> = Actor<\n ReturnType<typeof createAuthMachine<TIdpClaims>>\n>;\n\n/** App-wide injection key for the auth actor created by the createAuth plugin. */\nexport const AUTH_INJECTION_KEY: InjectionKey<AuthActor> = Symbol(\"ricardoqmd-auth\");\n","import type { App, Plugin } from \"vue\";\nimport { createActor } from \"xstate\";\nimport { createAuthMachine, type AuthProvider } from \"@ricardoqmd/auth-core\";\nimport { AUTH_INJECTION_KEY, type AuthActor } from \"./injection-key.js\";\n\nexport interface CreateAuthOptions<TIdpClaims = unknown> {\n /** Adapter instance (e.g. createKeycloakProvider()). IDP-agnostic. */\n provider: AuthProvider<TIdpClaims>;\n}\n\n/**\n * Vue plugin wiring @ricardoqmd/auth-core into a Vue app.\n *\n * Creates ONE auth actor per app instance (SSR-safe: never a module-level\n * singleton), starts it, kicks off initialization, and provides it app-wide\n * for useAuth(). Scope is SPA/client-only (ADR-012): the eager INIT below\n * assumes a browser. SSR-ready by construction (per-app instance) but not\n * SSR-supported.\n */\nexport function createAuth<TIdpClaims = unknown>(\n options: CreateAuthOptions<TIdpClaims>,\n): Plugin {\n return {\n install(app: App) {\n const machine = createAuthMachine<TIdpClaims>(options.provider);\n const actor = createActor(machine);\n actor.start();\n actor.send({ type: \"INIT\" });\n app.provide(AUTH_INJECTION_KEY, actor as AuthActor);\n // Stop the actor when the app unmounts (avoids leaks in tests/HMR).\n // app.onUnmount requires Vue 3.5+; guard so older runtimes still install.\n const unmountable = app as App & { onUnmount?: (cb: () => void) => void };\n if (typeof unmountable.onUnmount === \"function\") {\n unmountable.onUnmount(() => actor.stop());\n }\n },\n };\n}\n","import { computed, inject, type ComputedRef } from \"vue\";\nimport { useSelector } from \"@xstate/vue\";\nimport type { AuthError, AuthUserClaims } from \"@ricardoqmd/auth-core\";\nimport { AUTH_INJECTION_KEY } from \"./injection-key.js\";\n\n/** Reactive auth state + helpers returned by useAuth(). */\nexport interface AuthState<TIdpClaims = unknown> {\n isLoading: ComputedRef<boolean>;\n isAuthenticated: ComputedRef<boolean>;\n token: ComputedRef<string | null>;\n user: ComputedRef<AuthUserClaims | null>;\n idpClaims: ComputedRef<TIdpClaims | null>;\n error: ComputedRef<AuthError | null>;\n login: () => void;\n logout: () => void;\n hasRole: (role: string) => boolean;\n hasAnyRole: (roles: string[]) => boolean;\n}\n\n/**\n * Reactive authentication state for the current app.\n * Must be called inside a component of an app that installed createAuth().\n */\nexport function useAuth<TIdpClaims = unknown>(): AuthState<TIdpClaims> {\n const actor = inject(AUTH_INJECTION_KEY);\n if (!actor) {\n throw new Error(\n \"useAuth() must be called within an app that installed the auth plugin. \" +\n \"Did you forget app.use(createAuth({ provider }))?\",\n );\n }\n\n const stateValue = useSelector(actor, (s) => s.value);\n const context = useSelector(actor, (s) => s.context);\n\n const isAuthenticated = computed(\n () =>\n typeof stateValue.value === \"object\" &&\n stateValue.value !== null &&\n \"authenticated\" in stateValue.value,\n );\n\n const topState = computed(() =>\n typeof stateValue.value === \"string\"\n ? stateValue.value\n : Object.keys(stateValue.value)[0],\n );\n\n const isLoading = computed(\n () => topState.value === \"initializing\" || topState.value === \"loggingOut\",\n );\n\n const token = computed(() => context.value.token);\n const user = computed(() => context.value.user);\n const idpClaims = computed(() => context.value.idpClaims as TIdpClaims | null);\n const error = computed(() => context.value.error);\n\n const login = () => actor.send({ type: \"LOGIN\" });\n const logout = () => actor.send({ type: \"LOGOUT\" });\n\n const hasRole = (role: string) =>\n user.value?.roles?.includes(role) ?? false;\n const hasAnyRole = (roles: string[]) =>\n roles.some((r) => user.value?.roles?.includes(r) ?? false);\n\n return {\n isLoading,\n isAuthenticated,\n token,\n user,\n idpClaims,\n error,\n login,\n logout,\n hasRole,\n hasAnyRole,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/injection-key.ts","../src/plugin.ts","../src/composable.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { InjectionKey } from \"vue\";\nimport type { Actor } from \"xstate\";\nimport type { createAuthMachine } from \"@ricardoqmd/auth-core\";\n\n/** The started auth actor type, derived from the core machine factory. */\nexport type AuthActor<TIdpClaims = unknown> = Actor<\n ReturnType<typeof createAuthMachine<TIdpClaims>>\n>;\n\n/** App-wide injection key for the auth actor created by the createAuth plugin. */\nexport const AUTH_INJECTION_KEY: InjectionKey<AuthActor> = Symbol(\"ricardoqmd-auth\");\n","import type { App, Plugin } from \"vue\";\nimport { createActor } from \"xstate\";\nimport {\n createAuthHandle,\n createAuthMachine,\n type AuthHandle,\n type AuthProvider,\n} from \"@ricardoqmd/auth-core\";\nimport { AUTH_INJECTION_KEY, type AuthActor } from \"./injection-key.js\";\n\nexport interface CreateAuthOptions<TIdpClaims = unknown> {\n /** Adapter instance (e.g. createKeycloakProvider()). IDP-agnostic. */\n provider: AuthProvider<TIdpClaims>;\n}\n\n/**\n * Wire @ricardoqmd/auth-core into a Vue app.\n *\n * Returns a value that is BOTH a Vue plugin and an imperative AuthHandle:\n * - `app.use(auth)` installs it (provides the actor for useAuth()).\n * - the same `auth` object exposes whenReady()/isAuthenticated()/hasRole()/... for\n * use OUTSIDE components — a vue-router `beforeEach` guard or an HTTP interceptor,\n * where useAuth() cannot run.\n *\n * Creates ONE actor per call (SSR-safe: never a module-level singleton). The actor\n * is started and initialized here, so the handle is usable before mount (guards run\n * before the app renders). Scope is SPA/client-only (ADR-012): the eager INIT\n * assumes a browser; SSR-ready by construction but not a supported target in 0.x.\n *\n * @example\n * const auth = createAuth({ provider });\n * app.use(auth);\n * router.beforeEach(async (to) => {\n * await auth.whenReady();\n * if (to.meta.requiresAuth && !auth.isAuthenticated()) return { name: \"login\" };\n * if (to.meta.roles && !auth.hasAnyRole(to.meta.roles as string[])) {\n * return { name: \"forbidden\" };\n * }\n * });\n */\nexport function createAuth<TIdpClaims = unknown>(\n options: CreateAuthOptions<TIdpClaims>,\n): Plugin & AuthHandle<TIdpClaims> {\n const machine = createAuthMachine<TIdpClaims>(options.provider);\n const actor = createActor(machine);\n actor.start();\n actor.send({ type: \"INIT\" });\n const handle = createAuthHandle<TIdpClaims>(actor);\n\n return {\n ...handle,\n install(app: App) {\n app.provide(AUTH_INJECTION_KEY, actor as AuthActor);\n // Stop the actor when the app unmounts (avoids leaks in tests/HMR).\n // app.onUnmount requires Vue 3.5+; guard so older runtimes still install.\n const unmountable = app as App & { onUnmount?: (cb: () => void) => void };\n if (typeof unmountable.onUnmount === \"function\") {\n unmountable.onUnmount(() => actor.stop());\n }\n },\n };\n}\n","import { computed, inject, type ComputedRef } from \"vue\";\nimport { useSelector } from \"@xstate/vue\";\nimport type { AuthError, AuthUserClaims } from \"@ricardoqmd/auth-core\";\nimport { AUTH_INJECTION_KEY } from \"./injection-key.js\";\n\n/** Reactive auth state + helpers returned by useAuth(). */\nexport interface AuthState<TIdpClaims = unknown> {\n isLoading: ComputedRef<boolean>;\n isAuthenticated: ComputedRef<boolean>;\n token: ComputedRef<string | null>;\n user: ComputedRef<AuthUserClaims | null>;\n idpClaims: ComputedRef<TIdpClaims | null>;\n error: ComputedRef<AuthError | null>;\n login: () => void;\n logout: () => void;\n hasRole: (role: string) => boolean;\n hasAnyRole: (roles: string[]) => boolean;\n}\n\n/**\n * Reactive authentication state for the current app.\n * Must be called inside a component of an app that installed createAuth().\n */\nexport function useAuth<TIdpClaims = unknown>(): AuthState<TIdpClaims> {\n const actor = inject(AUTH_INJECTION_KEY);\n if (!actor) {\n throw new Error(\n \"useAuth() must be called within an app that installed the auth plugin. \" +\n \"Did you forget app.use(createAuth({ provider }))?\",\n );\n }\n\n const stateValue = useSelector(actor, (s) => s.value);\n const context = useSelector(actor, (s) => s.context);\n\n const isAuthenticated = computed(\n () =>\n typeof stateValue.value === \"object\" &&\n stateValue.value !== null &&\n \"authenticated\" in stateValue.value,\n );\n\n const topState = computed(() =>\n typeof stateValue.value === \"string\"\n ? stateValue.value\n : Object.keys(stateValue.value)[0],\n );\n\n const isLoading = computed(\n () => topState.value === \"initializing\" || topState.value === \"loggingOut\",\n );\n\n const token = computed(() => context.value.token);\n const user = computed(() => context.value.user);\n const idpClaims = computed(() => context.value.idpClaims as TIdpClaims | null);\n const error = computed(() => context.value.error);\n\n const login = () => actor.send({ type: \"LOGIN\" });\n const logout = () => actor.send({ type: \"LOGOUT\" });\n\n const hasRole = (role: string) =>\n user.value?.roles?.includes(role) ?? false;\n const hasAnyRole = (roles: string[]) =>\n roles.some((r) => user.value?.roles?.includes(r) ?? false);\n\n return {\n isLoading,\n isAuthenticated,\n token,\n user,\n idpClaims,\n error,\n login,\n logout,\n hasRole,\n hasAnyRole,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ricardoqmd/auth-vue",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Vue 3 client-side bindings for @ricardoqmd/auth-core — createAuth plugin, useAuth composable, RBAC helpers",
5
5
  "keywords": [
6
6
  "auth",
@@ -40,7 +40,7 @@
40
40
  "@xstate/vue": "^5.0.0"
41
41
  },
42
42
  "peerDependencies": {
43
- "@ricardoqmd/auth-core": "^1.0.0",
43
+ "@ricardoqmd/auth-core": "^1.1.0",
44
44
  "vue": "^3.5.0",
45
45
  "xstate": "^5.20.0"
46
46
  },
@@ -52,7 +52,7 @@
52
52
  "tsup": "^8.3.5",
53
53
  "typescript": "^5.6.3",
54
54
  "vitest": "^4.1.5",
55
- "@ricardoqmd/auth-core": "1.0.0"
55
+ "@ricardoqmd/auth-core": "^1.1.0"
56
56
  },
57
57
  "publishConfig": {
58
58
  "access": "public"