@flow-industries/id 0.15.0 → 0.16.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.
@@ -81,6 +81,13 @@ export function createProfileButton(options) {
81
81
  ? { username: user.username, isGuest: user.isGuest === true }
82
82
  : null;
83
83
  }
84
+ // The widget's only credential. `getToken` refreshes a token that is missing
85
+ // or near expiry, so a widget open on a page left idle past the 1h access-
86
+ // token lifetime still gets a live one instead of a 401.
87
+ async function answerTokenRequest(bridge, id) {
88
+ const token = await flow.getToken().catch(() => null);
89
+ void bridge.send("__internal", { type: "profile-token", id, token });
90
+ }
84
91
  // ----- the persistent pill (stays mounted; sizes to its own content) -----
85
92
  const pill = makeFrame();
86
93
  let pillWidth = 0;
@@ -105,6 +112,9 @@ export function createProfileButton(options) {
105
112
  else if (payload.type === "profile-expand") {
106
113
  showDialog();
107
114
  }
115
+ else if (payload.type === "profile-token-request") {
116
+ void answerTokenRequest(pillBridge, payload.id);
117
+ }
108
118
  });
109
119
  // ----- the dialog overlay (lazy; a separate fullscreen iframe layered ABOVE
110
120
  // the pill, so the pill shows through its dimmed backdrop) -----
@@ -131,6 +141,10 @@ export function createProfileButton(options) {
131
141
  hideDialog();
132
142
  void flow.logout().catch(() => { });
133
143
  }
144
+ else if (payload.type === "profile-token-request") {
145
+ if (dialogBridge)
146
+ void answerTokenRequest(dialogBridge, payload.id);
147
+ }
134
148
  });
135
149
  void dialogBridge.send("__internal", {
136
150
  type: "profile-identity",
@@ -1,4 +1,5 @@
1
1
  import type { JWTPayload } from "jose";
2
+ import type { AuthEventName } from "./events";
2
3
  export type FlowUser = {
3
4
  id: string;
4
5
  username: string;
@@ -14,6 +15,17 @@ export type FlowCredential = {
14
15
  id: string;
15
16
  publicKey: string;
16
17
  };
18
+ /** The better-auth session user with Flow's additional fields — the one
19
+ * shape routes cast `session.user` to instead of ad-hoc inline casts. */
20
+ export interface SessionUser {
21
+ id: string;
22
+ email?: string | null;
23
+ emailVerified?: boolean;
24
+ username?: string | null;
25
+ isGuest?: boolean;
26
+ role?: string | null;
27
+ image?: string | null;
28
+ }
17
29
  /**
18
30
  * A session minted for one of the ADDITIONAL audiences a login requested
19
31
  * (AUTH-39): its own refresh-token lineage and access JWT, both bound to
@@ -61,6 +73,56 @@ export type VerifyOptions = {
61
73
  audience: string;
62
74
  issuerUrl?: string;
63
75
  };
76
+ /**
77
+ * One entry in the signed-in user's session list (`GET /api/account/sessions`).
78
+ * An entry is either a cookie session (with the activity of its linked app
79
+ * refresh lineages folded in) or an app-only refresh lineage whose cookie
80
+ * session no longer exists. `id` is an opaque revocation handle — never a
81
+ * token, and never another user's row.
82
+ */
83
+ export type AccountSession = {
84
+ id: string;
85
+ /** The app origin the session was opened for; null on a pre-audience row. */
86
+ audience: string | null;
87
+ /** Coarse "Browser · OS" from the sign-in user agent; null when unknown. */
88
+ device: string | null;
89
+ createdAt: string;
90
+ /** Latest activity across the cookie session and its app refresh lineages. */
91
+ lastSeenAt: string;
92
+ expiresAt: string;
93
+ /** True for the session serving this request. */
94
+ isCurrent: boolean;
95
+ };
96
+ export type AccountSessionsResponse = {
97
+ sessions: AccountSession[];
98
+ };
99
+ /** Result of `DELETE /api/account/sessions/:id`. */
100
+ export type RevokeSessionResponse = {
101
+ revoked: true;
102
+ /** True when the caller revoked the session serving the request itself. */
103
+ wasCurrent: boolean;
104
+ };
105
+ /** Result of `POST /api/account/sessions/revoke-all`. */
106
+ export type RevokeAllSessionsResponse = {
107
+ revokedSessions: number;
108
+ revokedLineages: number;
109
+ };
110
+ /**
111
+ * One entry of the user-facing security feed
112
+ * (`GET /api/account/security-events`). Country and coarse device only —
113
+ * the stored ip and raw user agent never leave the server.
114
+ */
115
+ export type SecurityActivityEntry = {
116
+ id: string;
117
+ event: AuthEventName;
118
+ audience: string | null;
119
+ country: string | null;
120
+ device: string | null;
121
+ createdAt: string;
122
+ };
123
+ export type SecurityActivityResponse = {
124
+ events: SecurityActivityEntry[];
125
+ };
64
126
  export type VerifiedFlowJWT = JWTPayload & {
65
127
  sub: string;
66
128
  username?: string;
@@ -1,3 +1,16 @@
1
+ /**
2
+ * Async view state for a dialog panel — the three states every fetched
3
+ * section renders (spinner / retry row / content), so "empty" stays
4
+ * distinguishable from "not loaded yet".
5
+ */
6
+ export type Loadable<T> = {
7
+ status: "loading";
8
+ } | {
9
+ status: "error";
10
+ } | {
11
+ status: "ready";
12
+ data: T;
13
+ };
1
14
  export type DialogReferrer = {
2
15
  icon?: string | {
3
16
  light: string;
@@ -4,12 +4,13 @@
4
4
  * here are the contract every dashboard/alert query depends on — changing one
5
5
  * is a breaking change to the dashboard.
6
6
  */
7
+ import type { GlobalRole } from "./rooms";
7
8
  export type AuthOutcome = "success" | "failure" | "info";
8
9
  export type AuthMode = "sign-up" | "sign-in";
9
10
  /** Client-only funnel steps reported via the `/api/events` beacon. */
10
11
  export type FunnelStep = "mode_selected" | "email_entered" | "ceremony_started" | "ceremony_failed" | "done_shown";
11
- export type AuthEventName = "auth.username.checked" | "auth.signup.succeeded" | "auth.signup.failed" | "auth.otp.sent" | "auth.otp.verified" | "auth.otp.failed" | "auth.email.verify.sent" | "auth.email.verify.succeeded" | "auth.email.verify.failed" | "auth.email.changed" | "auth.avatar.upload.succeeded" | "auth.avatar.upload.failed" | "auth.challenge.issued" | "auth.signin.succeeded" | "auth.signin.failed" | "auth.passkey.added" | "auth.passkey.add_failed" | "auth.restore.succeeded" | "auth.restore.failed" | "auth.restore.no_session" | "auth.refresh.succeeded" | "auth.refresh.failed" | "auth.refresh.reuse" | "auth.refresh.grace" | "auth.jwt.verified" | "auth.jwt.rejected" | "auth.signout" | "auth.audience.rejected" | "auth.audience.minted" | "auth.guest.created" | "auth.guest.restored" | "auth.guest.upgraded" | "auth.guest.failed" | "auth.funnel.mode_selected" | "auth.funnel.email_entered" | "auth.funnel.ceremony_started" | "auth.funnel.ceremony_failed" | "auth.funnel.done_shown";
12
- export type AuthErrorCode = "username_taken" | "credential_taken" | "email_taken" | "email_not_verified" | "no_email" | "image_upload_forbidden" | "image_upload_rate_limited" | "invalid_image_type" | "image_too_large" | "image_upload_failed" | "otp_invalid" | "otp_expired" | "otp_attempts_exceeded" | "otp_resend_cooldown" | "otp_resend_limit" | "otp_global_limit" | "otp_send_failed" | "challenge_expired" | "unknown_credential" | "invalid_assertion_type" | "invalid_assertion_origin" | "user_verification_required" | "invalid_signature" | "user_not_found" | "missing_audience" | "audience_not_allowed" | "audience_mismatch" | "no_session" | "no_passkey" | "refresh_token_invalid" | "refresh_token_expired" | "refresh_epoch_stale" | "refresh_reuse_detected" | "guest_rate_limited" | "guest_global_limit" | "guest_username_exhausted" | "guest_session" | "passkey_exists" | "already_upgraded" | "malformed_token" | "unknown_key" | "verification_failed" | "webauthn_not_allowed" | "webauthn_security" | "webauthn_invalid_state" | "webauthn_not_supported" | "webauthn_constraint" | "webauthn_aborted" | "webauthn_unknown" | "internal_error";
12
+ export type AuthEventName = "auth.username.checked" | "auth.signup.succeeded" | "auth.signup.failed" | "auth.otp.sent" | "auth.otp.verified" | "auth.otp.failed" | "auth.email.verify.sent" | "auth.email.verify.succeeded" | "auth.email.verify.failed" | "auth.email.changed" | "auth.avatar.upload.succeeded" | "auth.avatar.upload.failed" | "auth.challenge.issued" | "auth.signin.succeeded" | "auth.signin.failed" | "auth.passkey.added" | "auth.passkey.add_failed" | "auth.restore.succeeded" | "auth.restore.failed" | "auth.restore.no_session" | "auth.refresh.succeeded" | "auth.refresh.failed" | "auth.refresh.reuse" | "auth.refresh.grace" | "auth.jwt.verified" | "auth.jwt.rejected" | "auth.signout" | "auth.session.revoked" | "auth.session.revoked_all" | "auth.audience.rejected" | "auth.audience.minted" | "auth.guest.created" | "auth.guest.restored" | "auth.guest.upgraded" | "auth.guest.failed" | "auth.role.granted" | "auth.role.revoked" | "auth.funnel.mode_selected" | "auth.funnel.email_entered" | "auth.funnel.ceremony_started" | "auth.funnel.ceremony_failed" | "auth.funnel.done_shown";
13
+ export type AuthErrorCode = "username_taken" | "credential_taken" | "email_taken" | "email_not_verified" | "no_email" | "image_upload_forbidden" | "image_upload_rate_limited" | "invalid_image_type" | "image_too_large" | "image_upload_failed" | "otp_invalid" | "otp_expired" | "otp_attempts_exceeded" | "otp_resend_cooldown" | "otp_resend_limit" | "otp_global_limit" | "otp_send_failed" | "challenge_expired" | "unknown_credential" | "invalid_assertion_type" | "invalid_assertion_origin" | "user_verification_required" | "invalid_signature" | "user_not_found" | "missing_audience" | "audience_not_allowed" | "audience_mismatch" | "no_session" | "no_passkey" | "refresh_token_invalid" | "refresh_token_expired" | "refresh_epoch_stale" | "refresh_user_epoch_stale" | "refresh_reuse_detected" | "guest_rate_limited" | "guest_global_limit" | "guest_username_exhausted" | "guest_session" | "passkey_exists" | "already_upgraded" | "malformed_token" | "unknown_key" | "verification_failed" | "webauthn_not_allowed" | "webauthn_security" | "webauthn_invalid_state" | "webauthn_not_supported" | "webauthn_constraint" | "webauthn_aborted" | "webauthn_unknown" | "internal_error";
13
14
  /** One flat record per event = one row in the `auth_events` stream. */
14
15
  export interface AuthEventRecord {
15
16
  service: "auth";
@@ -26,6 +27,11 @@ export interface AuthEventRecord {
26
27
  mode: AuthMode | null;
27
28
  step: FunnelStep | null;
28
29
  errorCode: AuthErrorCode | null;
30
+ /** Role-audit fields: who changed whose global role, from what, to what.
31
+ * `userId`/`username` carry the target. */
32
+ actorId: string | null;
33
+ roleFrom: GlobalRole | null;
34
+ roleTo: GlobalRole | null;
29
35
  ip: string | null;
30
36
  country: string | null;
31
37
  userAgent: string | null;
@@ -1,10 +1,10 @@
1
- export type { AdditionalSession, AuthConfig, AuthResponse, AuthResponseWithWebAuthn, FlowCredential, FlowUser, PasskeyPluginOptions, VerifiedFlowJWT, VerifyOptions, WebAuthnSignature, } from "./auth";
2
- export type { BoundaryError, DialogCustomFeatures, DialogCustomLabels, DialogError, DialogReferrer, DialogState, ProfileData, ProfileIdentity, } from "./dialog";
1
+ export type { AccountSession, AccountSessionsResponse, AdditionalSession, AuthConfig, AuthResponse, AuthResponseWithWebAuthn, FlowCredential, FlowUser, PasskeyPluginOptions, RevokeAllSessionsResponse, RevokeSessionResponse, SecurityActivityEntry, SecurityActivityResponse, SessionUser, VerifiedFlowJWT, VerifyOptions, WebAuthnSignature, } from "./auth";
2
+ export type { BoundaryError, DialogCustomFeatures, DialogCustomLabels, DialogError, DialogReferrer, DialogState, Loadable, ProfileData, ProfileIdentity, } from "./dialog";
3
3
  export type { AuthErrorCode, AuthEventName, AuthEventRecord, AuthMode, AuthOutcome, BeaconBody, FunnelStep, } from "./events";
4
4
  export type { Bridge, BridgeParameters, FlowAccount, FlowRemote, FlowRemoteConfig, FromWindowOptions, MessageResponse, Messenger, OneOf, Payload, QueuedRequest, ReadyOptions, RemoteFlowState, RemoteState, Schema, Storage, Topic, WithReady, } from "./messenger";
5
5
  export type { Call, ConnectCapabilities, ConnectRequest, ConnectResponse, GuestRequest, GuestResponse, MethodName, MethodParams, MethodResult, RestoreResponse, RpcRequest, SendCallsParams, SendCallsRequest, SendCallsResponse, SendTransactionParams, SendTransactionRequest, SendTransactionResponse, SignMessageRequest, SignMessageResponse, SignOutRequest, SignOutResponse, SignTypedDataRequest, SignTypedDataResponse, TransactionArgs, TypedData, TypedDataDomain, TypedDataField, } from "./protocol";
6
6
  export { getConnectCapabilities, isPersonalSignParams, isSendCallsParams, isSendTransactionParams, } from "./protocol";
7
- export type { CreateRoomInput, MyRooms, RoomChannel, RoomDetail, RoomList, RoomMemberEntry, RoomMembers, RoomOccupancy, RoomOwner, RoomPresenceEntry, RoomPresenceSnapshot, RoomRestrictionKind, RoomRole, RoomSummary, RoomSurfaceSettings, RoomsApi, RoomVisibility, UpdateRoomInput, VerifyRoomContext, } from "./rooms";
7
+ export type { CreateRoomInput, GlobalRole, ModerationSubject, MyRooms, RoleChangeVerdict, RoomChannel, RoomDetail, RoomList, RoomMemberEntry, RoomMembers, RoomOccupancy, RoomOwner, RoomPresenceEntry, RoomPresenceSnapshot, RoomRestrictionKind, RoomRole, RoomSummary, RoomSurfaceSettings, RoomsApi, RoomVisibility, StaffEntry, UpdateRoomInput, VerifyRoomContext, } from "./rooms";
8
8
  export type { AccessKeyOptions, AccessKeyPreparation, Address, CreateDialogHostOptions, CreateFlowOptions, DialogHost, DialogOpenOptions, FinalizeAccessKeyParams, Flow, FlowConnectorParameters, FlowCookieNames, FlowIdProviderProps, FlowSessionState, FlowState, Listener, LoginOptions, MountProfileOptions, PrepareArgsWithAuth, PrepareTransactionRequestPhase, ProfileButtonHandle, ProfileButtonProps, ProfilePosition, ResolveAccountParams, ResolvedAccessKeyOptions, RootCredential, RunLoginParams, RunLoginResult, SendCallsArgs, SendTransactionArgs, Session, SigningContext, SignMessageArgs, SignTypedDataArgs, Store, StoredAccessKey, WagmiConnectCapabilities, WagmiConnectParams, } from "./sdk";
9
9
  export type { ResolvedFlowSession, ResolveSessionOptions, SessionRouteOptions, SessionRouteResponse, SessionRouteSession, } from "./server";
10
10
  export type { CoinAsset, IdentifiedTx, TxApprove, TxConvert, TxSend, TxSwap, } from "./tx";
@@ -90,6 +90,13 @@ export type Schema = [
90
90
  username: string;
91
91
  isGuest: boolean;
92
92
  } | null;
93
+ } | {
94
+ type: "profile-token-request";
95
+ id: string;
96
+ } | {
97
+ type: "profile-token";
98
+ id: string;
99
+ token: string | null;
93
100
  } | {
94
101
  type: "profile-login";
95
102
  mode: "signin" | "signup";
@@ -5,6 +5,34 @@ export type RoomVisibility = "public" | "unlisted" | "private";
5
5
  * Member rows carry moderator|member.
6
6
  */
7
7
  export type RoomRole = "owner" | "moderator" | "member";
8
+ /**
9
+ * Global permission tier held on the user row, composing over room roles: an
10
+ * admin holds owner-power in any room, a platform moderator holds mod-power
11
+ * anywhere. Read from the DB at enforcement time — never a JWT claim — so a
12
+ * demotion is instant.
13
+ */
14
+ export type GlobalRole = "user" | "moderator" | "admin";
15
+ /** What the moderation lattice ranks: a subject's global tier plus their
16
+ * room-axis role (`owner` from `room.ownerId`, else the member row). */
17
+ export interface ModerationSubject {
18
+ globalRole: GlobalRole;
19
+ roomRole: RoomRole | null;
20
+ }
21
+ /** One row of `GET /api/admin/staff` — a user holding a non-`user` tier. */
22
+ export interface StaffEntry {
23
+ userId: string;
24
+ username: string;
25
+ role: GlobalRole;
26
+ }
27
+ /** Outcome of a global-role change request: refused with a reason, or
28
+ * allowed with the audit event it is (`null` for a no-op). */
29
+ export type RoleChangeVerdict = {
30
+ allowed: true;
31
+ event: "auth.role.granted" | "auth.role.revoked" | null;
32
+ } | {
33
+ allowed: false;
34
+ reason: "self_target" | "guest_target";
35
+ };
8
36
  export type RoomChannel = "game" | "voice" | "text";
9
37
  export type RoomRestrictionKind = "ban" | "mute";
10
38
  export interface RoomOwner {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flow-industries/id",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "main": "./dist/sdk/client/index.js",
5
5
  "module": "./dist/sdk/client/index.js",
6
6
  "types": "./dist/sdk/client/index.d.ts",
@@ -91,6 +91,7 @@
91
91
  "devDependencies": {
92
92
  "@better-auth/cli": "^1.4.17",
93
93
  "@biomejs/biome": "2.4.12",
94
+ "@electric-sql/pglite": "^0.5.4",
94
95
  "@flow-industries/lint": "0.1.0",
95
96
  "@react-email/ui": "^6.6.0",
96
97
  "@tailwindcss/vite": "^4.1.18",
@@ -112,7 +113,7 @@
112
113
  },
113
114
  "dependencies": {
114
115
  "@aws-sdk/client-s3": "^3.1073.0",
115
- "@flow-industries/ui": "^0.15.3",
116
+ "@flow-industries/ui": "^0.19.0",
116
117
  "@hono/otel": "^1.1.2",
117
118
  "@openobserve/browser-logs": "^0.3.1",
118
119
  "@openobserve/browser-rum": "^0.3.1",