@user-hub/auth 0.1.1 → 0.1.3

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
@@ -109,6 +109,17 @@ Run your dev server as usual and open it. With no token yet, it sends you to the
109
109
  - **Different port:** run `hub-user register` again with the new `--dev` URL. The hub only sends tokens to registered URLs.
110
110
  - **Production:** someone without a token sees "Scan your badge to sign in". Pass `signedOut={…}` to `<HubSignedIn>` to style that screen.
111
111
 
112
+ ## Sign out
113
+
114
+ ```tsx
115
+ import { useHubSignOut } from "@user-hub/auth/react"; // also from /convex-react
116
+
117
+ const signOut = useHubSignOut();
118
+ <button onClick={signOut}>Sign out</button>
119
+ ```
120
+
121
+ It forgets this app's token; other apps stay signed in. In production the person sees "Scan your badge to sign in". On a local dev server the hub opens so you can pick who to be next.
122
+
112
123
  ## Facilitator pages
113
124
 
114
125
  People are members or facilitators. Always check on the server, then hide what members cannot use:
@@ -158,7 +169,7 @@ Apps without Convex use `HubAuthProvider` and `HubSignedIn` from `@user-hub/auth
158
169
  | `@user-hub/auth/convex-react` | `ConvexHubAuthProvider` | For Convex apps, in place of `ConvexProvider`, inside `<body>`. Props: `client`, `app`, `hubUrl?` |
159
170
  | `@user-hub/auth/react` | `HubAuthProvider` | The same without Convex: `app`, `hubUrl?` |
160
171
  | both | `HubSignedIn` | Shows its children only when signed in. Props: `signedOut?`, `loading?` |
161
- | | `useHubUser()`, `useHubToken()` | The person (or null), the raw token (or null). Also from `/convex-react` |
172
+ | | `useHubUser()`, `useHubToken()`, `useHubSignOut()` | The person (or null), the raw token (or null), a sign-out function. Also from `/convex-react` |
162
173
  | `@user-hub/auth/server` | `verifyHubToken(requestOrToken, { app })` | Checks a token anywhere else |
163
174
  | CLI | `hub-user register --name <name> [--prod <url>] [--dev <url>] [--hub <url>] [--json]` | Registers or updates the app (at least one URL) |
164
175
 
@@ -171,4 +182,4 @@ bun run check # Biome
171
182
  bun run build # dist/: commit it
172
183
  ```
173
184
 
174
- Publish with `bun publish`: it builds and runs the tests first.
185
+ Release with `just release patch` (or `minor` / `major`): it bumps the version, publishes to npm and pushes the commit and tag. See the comments in the `justfile`.
@@ -1,6 +1,6 @@
1
1
  import { type ConvexReactClient } from "convex/react";
2
2
  import { type HubAuthProviderProps } from "./react";
3
- export { HubSignedIn, useHubSession, useHubToken, useHubUser, } from "./react";
3
+ export { HubSignedIn, useHubSession, useHubSignOut, useHubToken, useHubUser, } from "./react";
4
4
  export type { HubRole, HubUser } from "./token";
5
5
  /**
6
6
  * HubAuthProvider for apps on Convex: the hub token becomes the Convex
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  HubAuthProvider2,
3
3
  HubSignedIn2,
4
+ useHubSignOut2,
4
5
  useHubSession2,
5
6
  useHubUser2,
6
7
  useHubToken2
7
- } from "./convex-7n6dqf5r.js";
8
+ } from "./convex-zgnvvwvn.js";
8
9
 
9
10
  // src/convex-react.tsx
10
11
  import { ConvexProviderWithAuth } from "convex/react";
@@ -36,6 +37,7 @@ export {
36
37
  ConvexHubAuthProvider,
37
38
  HubSignedIn2 as HubSignedIn,
38
39
  useHubSession2 as useHubSession,
40
+ useHubSignOut2 as useHubSignOut,
39
41
  useHubToken2 as useHubToken,
40
42
  useHubUser2 as useHubUser
41
43
  };
@@ -1,6 +1,7 @@
1
1
  // src/react.tsx
2
2
  import {
3
3
  createContext,
4
+ useCallback,
4
5
  useContext,
5
6
  useEffect,
6
7
  useMemo,
@@ -41,7 +42,8 @@ var MAX_TIMEOUT_MS = 2 ** 31 - 1;
41
42
  var LOCAL_HOST = /^(localhost|127(\.\d{1,3}){3}|10(\.\d{1,3}){3}|192\.168(\.\d{1,3}){2}|172\.(1[6-9]|2\d|3[01])(\.\d{1,3}){2}|\[::1\]|[a-z0-9-]+\.local)$/i;
42
43
  var HubContext = createContext({
43
44
  session: undefined,
44
- redirecting: false
45
+ redirecting: false,
46
+ signOut: () => {}
45
47
  });
46
48
  var storageKey = (app) => `user-hub-token:${app}`;
47
49
  function readStorage(key) {
@@ -95,7 +97,11 @@ function HubAuthProvider2({
95
97
  window.location.replace(`${hubUrl.replace(/\/+$/, "")}/open/${encodeURIComponent(app)}`);
96
98
  }
97
99
  }, [redirecting, hubUrl, app]);
98
- const state = useMemo(() => ({ session, redirecting }), [session, redirecting]);
100
+ const signOut = useCallback(() => {
101
+ writeStorage(storageKey(app), null);
102
+ setSession(null);
103
+ }, [app]);
104
+ const state = useMemo(() => ({ session, redirecting, signOut }), [session, redirecting, signOut]);
99
105
  return /* @__PURE__ */ jsx(HubContext.Provider, {
100
106
  value: state,
101
107
  children
@@ -117,6 +123,9 @@ function HubSignedIn2({
117
123
  return loading;
118
124
  return session ? children : signedOut;
119
125
  }
126
+ function useHubSignOut2() {
127
+ return useContext(HubContext).signOut;
128
+ }
120
129
  function useHubSession2() {
121
130
  return useContext(HubContext).session;
122
131
  }
@@ -128,4 +137,4 @@ function useHubToken2() {
128
137
  return useHubSession2()?.token ?? null;
129
138
  }
130
139
 
131
- export { HubAuthProvider2, HubSignedIn2, useHubSession2, useHubUser2, useHubToken2 };
140
+ export { HubAuthProvider2, HubSignedIn2, useHubSignOut2, useHubSession2, useHubUser2, useHubToken2 };
package/dist/react.d.ts CHANGED
@@ -22,6 +22,12 @@ export declare function HubSignedIn({ children, signedOut, loading, }: {
22
22
  signedOut?: ReactNode;
23
23
  loading?: ReactNode;
24
24
  }): ReactNode;
25
+ /**
26
+ * Signs the person out of this app: `const signOut = useHubSignOut()`. They
27
+ * need to scan their badge again (or, on a dev server, pick a person at the
28
+ * hub). Other apps stay signed in.
29
+ */
30
+ export declare function useHubSignOut(): () => void;
25
31
  /** The current session: undefined while loading, null when signed out. */
26
32
  export declare function useHubSession(): HubSession | null | undefined;
27
33
  /** The signed-in person, or null. */
package/dist/react.js CHANGED
@@ -1,14 +1,16 @@
1
1
  import {
2
2
  HubAuthProvider2,
3
3
  HubSignedIn2,
4
+ useHubSignOut2,
4
5
  useHubSession2,
5
6
  useHubUser2,
6
7
  useHubToken2
7
- } from "./convex-7n6dqf5r.js";
8
+ } from "./convex-zgnvvwvn.js";
8
9
  export {
9
10
  HubAuthProvider2 as HubAuthProvider,
10
11
  HubSignedIn2 as HubSignedIn,
11
12
  useHubSession2 as useHubSession,
13
+ useHubSignOut2 as useHubSignOut,
12
14
  useHubToken2 as useHubToken,
13
15
  useHubUser2 as useHubUser
14
16
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@user-hub/auth",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Sign people in from User Hub badges: Convex auth config, React provider, server verification and the hub-user CLI.",
5
5
  "type": "module",
6
6
  "files": [
@@ -51,6 +51,7 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "@biomejs/biome": "2.5.15",
54
+ "@happy-dom/global-registrator": "^20.14.5",
54
55
  "@types/bun": "^1.4.2",
55
56
  "@types/react": "^19.3.0",
56
57
  "@types/react-dom": "^19.3.0",
@@ -7,9 +7,33 @@ description: Sign people into an app from their User Hub badge. Use when an app
7
7
 
8
8
  People at a workshop scan a printed QR badge. The hub sends them to whichever app is live, signed in, with a token in the URL fragment (`#token=…`) that only that app accepts. This package turns that token into a signed-in person. The app keeps its own data, keyed on the person's hub id.
9
9
 
10
- ## The hub
10
+ ## Starting a new app
11
11
 
12
- Ask the human for the hub's URL. It is `HUB_URL` for `hub-user`, the `HUB_ISSUER` the app trusts and the `VITE_HUB_URL` its frontend uses.
12
+ If there is no app yet, don't build one by hand: scaffold one with sign-in already wired (TanStack Start + Convex, sign-in and sign-out, a facilitator page, `CLAUDE.md` and this skill):
13
+
14
+ ```sh
15
+ bun create @user-hub my-app
16
+ ```
17
+
18
+ In a terminal it asks for the hub's URL and admin password and saves them to the new app's `.env.local`. As an agent you never handle the password, so either ask the human to run it, or run it only when `HUB_URL` and `HUB_ADMIN_PASSWORD` are already in the environment (for example `infisical run -- bun create @user-hub my-app`). Never pass `--admin-password` with a value you typed yourself.
19
+
20
+ Then:
21
+
22
+ ```sh
23
+ cd my-app
24
+ bun run setup # Convex project, hub registration, HUB_ISSUER and HUB_JWKS, once
25
+ bun run dev # http://localhost:3000
26
+ ```
27
+
28
+ That covers every step below; read on only to add User Hub to an existing app or to change the wiring.
29
+
30
+ ## The hub's URL
31
+
32
+ The hub has one address, for example `https://hub.example.com`. The human gives it to you; you need it in three places, all with the same value:
33
+
34
+ - `HUB_URL` in the app's `.env` or `.env.local`: read by `hub-user register` (**Register the app**). The human puts it there with the admin password.
35
+ - `HUB_ISSUER` on the app's Convex deployment: `hub-user register` prints it for you (**Set the two values**).
36
+ - `VITE_HUB_URL` in the app's frontend env: lets the dev server send you to the hub to sign in (**Wire it up**).
13
37
 
14
38
  ## Before you start: ask the human
15
39
 
@@ -31,10 +55,10 @@ You need four things. Ask for them; never guess.
31
55
  bun add @user-hub/auth
32
56
  ```
33
57
 
34
- 2. **Register the app** from the app's repo root, with the secrets supplied as the human chose in step 3 above:
58
+ 2. **Register the app** from the app's repo root, with `HUB_URL` and the password supplied the way the human chose in question 3 above:
35
59
 
36
60
  ```sh
37
- bunx hub-user register --name "Voting" --prod https://app.example.com --dev http://localhost:3000
61
+ bunx hub-user register --name "<app name>" --prod https://app.example.com --dev http://localhost:3000
38
62
  ```
39
63
 
40
64
  Leave out `--prod` if the app is not deployed yet. Running it again with the same `--name` updates the URLs; a URL left out keeps the registered one. If it fails with "set HUB_URL", "set HUB_ADMIN_PASSWORD" or a wrong password, stop and ask the human; do not work around it.
@@ -45,7 +69,7 @@ You need four things. Ask for them; never guess.
45
69
  - Convex: `bunx convex env set HUB_ISSUER '…'` and `bunx convex env set HUB_JWKS '…'` (on every deployment the app uses).
46
70
  - Other servers: their environment, same names.
47
71
 
48
- 4. **Wire it up** with the key from step 2.
72
+ 4. **Wire it up** with the key from **Register the app**.
49
73
 
50
74
  Convex, `convex/auth.config.ts`:
51
75
 
@@ -90,6 +114,17 @@ Run the app as usual (`bun run dev`) on the `--dev` URL you registered. Opening
90
114
  - If the dev server runs on another port, run `hub-user register` again with the same `--name` and the new `--dev` URL: the hub only sends tokens back to registered URLs.
91
115
  - This only happens on local addresses (localhost, 127.x, 10.x, 172.16-31.x, 192.168.x, *.local). In production a person without a token sees "Scan your badge to sign in".
92
116
 
117
+ ## Sign out
118
+
119
+ Use `useHubSignOut()` from the same entry point as the provider; never clear storage or tokens yourself.
120
+
121
+ ```tsx
122
+ const signOut = useHubSignOut();
123
+ <button onClick={signOut}>Sign out</button>
124
+ ```
125
+
126
+ It forgets this app's token only. In production the person then sees the signed-out screen and scans their badge again; on a local dev server the hub opens to pick another person.
127
+
93
128
  ## Facilitator pages
94
129
 
95
130
  People are either `"member"` or `"facilitator"`, set in the hub. To give facilitators their own page or actions: