@iqauth/sdk 2.6.4 → 2.7.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 +173 -1
- package/dist/browser-session.d.mts +4 -4
- package/dist/browser-session.d.ts +4 -4
- package/dist/browser-session.js +181 -41
- package/dist/browser-session.mjs +3 -3
- package/dist/browser.d.mts +5 -5
- package/dist/browser.d.ts +5 -5
- package/dist/browser.js +271 -32
- package/dist/browser.mjs +5 -5
- package/dist/{chunk-6I6RM4MN.mjs → chunk-6PJRLRB4.mjs} +33 -3
- package/dist/{chunk-LIZYFXH7.mjs → chunk-DFWHSDYQ.mjs} +1 -1
- package/dist/chunk-GLXSIGVS.mjs +66 -0
- package/dist/{chunk-DJIBN2N7.mjs → chunk-GN37E64I.mjs} +29 -7
- package/dist/{chunk-WQWBJSSS.mjs → chunk-HVHNYPDC.mjs} +6 -6
- package/dist/{chunk-W3F4JYGP.mjs → chunk-JXQI62A7.mjs} +108 -18
- package/dist/{chunk-UNYDG2L4.mjs → chunk-NUO2I65G.mjs} +56 -23
- package/dist/chunk-PMAFENVI.mjs +229 -0
- package/dist/chunk-RR2MGPTK.mjs +2724 -0
- package/dist/{chunk-XAWYUPMO.mjs → chunk-RTJAIBXY.mjs} +220 -20
- package/dist/{chunk-6TDJJER7.mjs → chunk-RUJXRTEW.mjs} +164 -5
- package/dist/{chunk-3JULWS6F.mjs → chunk-WCELYTJ3.mjs} +3 -3
- package/dist/{chunk-MKKZULZR.mjs → chunk-WIFG74IK.mjs} +1 -1
- package/dist/{chunk-BVV54LPI.mjs → chunk-YVALAG3B.mjs} +10 -4
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/{client-kYlJFgPv.d.mts → client-BGFnBpfc.d.mts} +47 -4
- package/dist/{client-BNQe3AgF.d.ts → client-CDQ21LvW.d.ts} +47 -4
- package/dist/{doctor-YYNHNMLD.mjs → doctor-JAFXWU3X.mjs} +2 -2
- package/dist/errors-Jl1Jtm-6.d.mts +107 -0
- package/dist/errors-Jl1Jtm-6.d.ts +107 -0
- package/dist/{express-B6_1vBYZ.d.mts → express-CVNQEkOr.d.mts} +2 -2
- package/dist/{express-CHpfa7D_.d.ts → express-Piv2WhWM.d.ts} +2 -2
- package/dist/express.d.mts +7 -6
- package/dist/express.d.ts +7 -6
- package/dist/express.js +349 -52
- package/dist/express.mjs +39 -12
- package/dist/fastify.d.mts +2 -0
- package/dist/fastify.d.ts +2 -0
- package/dist/fastify.js +332 -52
- package/dist/fastify.mjs +23 -8
- package/dist/hono.d.mts +2 -0
- package/dist/hono.d.ts +2 -0
- package/dist/hono.js +329 -52
- package/dist/hono.mjs +20 -8
- package/dist/index-5KSZEnDe.d.ts +1626 -0
- package/dist/index-CKoZHAoc.d.mts +1626 -0
- package/dist/index.d.mts +56 -8
- package/dist/index.d.ts +56 -8
- package/dist/index.js +565 -69
- package/dist/index.mjs +29 -9
- package/dist/{keys-NLWFAOEM.mjs → keys-6Y776TG2.mjs} +2 -2
- package/dist/locales.d.mts +1 -1
- package/dist/locales.d.ts +1 -1
- package/dist/mobile.d.mts +77 -7
- package/dist/mobile.d.ts +77 -7
- package/dist/mobile.js +276 -41
- package/dist/mobile.mjs +98 -3
- package/dist/next.d.mts +2 -1
- package/dist/next.d.ts +2 -1
- package/dist/next.js +391 -201
- package/dist/next.mjs +22 -7
- package/dist/{provisioningBridge-DnTfzdZK.d.ts → provisioningBridge-CGpMRie4.d.ts} +1 -1
- package/dist/{provisioningBridge-88xjOS2n.d.mts → provisioningBridge-M5G47LWO.d.mts} +1 -1
- package/dist/{publishableKey-BaR0HoAH.d.ts → publishableKey-f2kq-rKw.d.mts} +1 -1
- package/dist/{publishableKey-BaR0HoAH.d.mts → publishableKey-f2kq-rKw.d.ts} +1 -1
- package/dist/react-permissions.d.mts +52 -0
- package/dist/react-permissions.d.ts +52 -0
- package/dist/react-permissions.js +239 -0
- package/dist/react-permissions.mjs +97 -0
- package/dist/react.d.mts +9 -1624
- package/dist/react.d.ts +9 -1624
- package/dist/react.js +313 -33
- package/dist/react.mjs +58 -2632
- package/dist/{reverify-4UEJXUS6.mjs → reverify-C64QXKJO.mjs} +2 -2
- package/dist/server/handlers.d.mts +148 -3
- package/dist/server/handlers.d.ts +148 -3
- package/dist/server/handlers.js +410 -11
- package/dist/server/handlers.mjs +12 -3
- package/dist/server.d.mts +151 -8
- package/dist/server.d.ts +151 -8
- package/dist/server.js +406 -50
- package/dist/server.mjs +93 -11
- package/dist/service.d.mts +4 -4
- package/dist/service.d.ts +4 -4
- package/dist/service.js +181 -41
- package/dist/service.mjs +3 -3
- package/dist/{signIn-OCr88Zf8.d.ts → signIn-BLFnz8SV.d.ts} +78 -3
- package/dist/{signIn-4OKLDEIH.mjs → signIn-SHBW6Z4T.mjs} +1 -1
- package/dist/{signIn-CiIBTJIh.d.mts → signIn-T-CZ6t6r.d.mts} +78 -3
- package/dist/test.mjs +3 -3
- package/dist/{tokens-DCyzzn8L.d.mts → tokens-Bqhmqq_R.d.ts} +9 -2
- package/dist/{tokens-aHiGFr_E.d.ts → tokens-CITeoG6P.d.mts} +9 -2
- package/dist/{types-6bNdxesb.d.ts → types-BdQ2lqfT.d.mts} +1 -1
- package/dist/{types-6bNdxesb.d.mts → types-BdQ2lqfT.d.ts} +1 -1
- package/dist/{types-DZAflmmq.d.mts → types-XOV9XPVi.d.mts} +99 -10
- package/dist/{types-DZAflmmq.d.ts → types-XOV9XPVi.d.ts} +99 -10
- package/dist/webhooks.d.mts +100 -17
- package/dist/webhooks.d.ts +100 -17
- package/dist/webhooks.js +164 -15
- package/dist/webhooks.mjs +7 -1
- package/dist/ws.d.mts +2 -2
- package/dist/ws.d.ts +2 -2
- package/dist/ws.js +80 -30
- package/dist/ws.mjs +4 -4
- package/docs/error-handling.md +101 -0
- package/docs/guides/effective-permissions.md +171 -0
- package/package.json +13 -3
- package/dist/chunk-UKZLOHZG.mjs +0 -83
- package/dist/errors-CDdl24MP.d.mts +0 -52
- package/dist/errors-CDdl24MP.d.ts +0 -52
package/dist/server.d.mts
CHANGED
|
@@ -1,10 +1,153 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { I as IQAuthClient } from './client-
|
|
3
|
-
export { E as ErrorCodes, I as IQAuthError } from './errors-
|
|
4
|
-
export { C as CookieAwareMiddlewareOptions, D as DEFAULT_ACCESS_COOKIE, a as DEFAULT_REFRESH_COOKIE, i as iqAuthMiddleware } from './express-
|
|
5
|
-
export { HandlerResponse, IQAuthHelperConfig, SetCookieDirective, handleCallback, handleRefresh, handleSignout, serializeCookie } from './server/handlers.mjs';
|
|
6
|
-
export { P as ProvisioningBridge, a as ProvisioningBridgeOptions, d as ProvisioningContext, b as ProvisioningStorage, c as createProvisioningBridge } from './provisioningBridge-
|
|
7
|
-
import './tokens-
|
|
1
|
+
import { J as JwtClaims, f as IQAuthTokenClientConfig, X as ExpressMiddlewareOptions, a as IQAuthRequestLike, b as IQAuthResponseLike, c as IQAuthNextFunction } from './types-XOV9XPVi.mjs';
|
|
2
|
+
import { I as IQAuthClient } from './client-BGFnBpfc.mjs';
|
|
3
|
+
export { E as ErrorCodes, I as IQAuthError } from './errors-Jl1Jtm-6.mjs';
|
|
4
|
+
export { C as CookieAwareMiddlewareOptions, D as DEFAULT_ACCESS_COOKIE, a as DEFAULT_REFRESH_COOKIE, i as iqAuthMiddleware } from './express-CVNQEkOr.mjs';
|
|
5
|
+
export { HandlerResponse, IQAuthHelperConfig, SetCookieDirective, UserinfoResponse, buildUserinfoResponse, handleCallback, handleRefresh, handleSignout, handleUserinfo, serializeCookie } from './server/handlers.mjs';
|
|
6
|
+
export { P as ProvisioningBridge, a as ProvisioningBridgeOptions, d as ProvisioningContext, b as ProvisioningStorage, c as createProvisioningBridge } from './provisioningBridge-M5G47LWO.mjs';
|
|
7
|
+
import './tokens-CITeoG6P.mjs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* linkLocalUserToIqAuthSub — first-time-migration helper.
|
|
11
|
+
*
|
|
12
|
+
* Use case: an app already had local users before adopting IQAuth. After a
|
|
13
|
+
* user signs in via IQAuth, the JWT's `sub` won't match any local row. The
|
|
14
|
+
* conventional self-heal is "look up by email, write the sub onto the matching
|
|
15
|
+
* row". Doing this safely requires:
|
|
16
|
+
*
|
|
17
|
+
* 1. An atomic transaction (find + write) so two concurrent first-time
|
|
18
|
+
* sign-ins for the same user can't both succeed.
|
|
19
|
+
* 2. Email matching that's case-insensitive by default (since email
|
|
20
|
+
* providers fold case in delivery).
|
|
21
|
+
* 3. A refusal to link when the candidate row already has a different
|
|
22
|
+
* non-null `iqauthSub` (i.e. that local row belongs to someone else).
|
|
23
|
+
* 4. A refusal to link when more than one local row matches the email
|
|
24
|
+
* (duplicate-email collision in the legacy table).
|
|
25
|
+
*
|
|
26
|
+
* The helper is ORM-agnostic. Pass any adapter that implements
|
|
27
|
+
* `LinkAdapter`. A reference Drizzle adapter is exported as
|
|
28
|
+
* `createDrizzleLinkAdapter`.
|
|
29
|
+
*
|
|
30
|
+
* Result codes:
|
|
31
|
+
* - 'linked' — wrote claims.sub onto the matched local row.
|
|
32
|
+
* - 'already_linked' — a row was already keyed by claims.sub. No write.
|
|
33
|
+
* - 'conflict' — matched row already has a different non-null sub,
|
|
34
|
+
* OR more than one local row matches the email.
|
|
35
|
+
* - 'not_found' — no local row matched any of the provided lookupBy
|
|
36
|
+
* keys. Caller should provision a new row separately.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
type LinkLookupBy = "email";
|
|
40
|
+
type LinkResult = {
|
|
41
|
+
status: "linked";
|
|
42
|
+
userId: string;
|
|
43
|
+
} | {
|
|
44
|
+
status: "already_linked";
|
|
45
|
+
userId: string;
|
|
46
|
+
} | {
|
|
47
|
+
status: "conflict";
|
|
48
|
+
userId?: string;
|
|
49
|
+
reason: "different_sub" | "duplicate_email";
|
|
50
|
+
} | {
|
|
51
|
+
status: "not_found";
|
|
52
|
+
};
|
|
53
|
+
interface LinkCandidate {
|
|
54
|
+
id: string;
|
|
55
|
+
email: string | null;
|
|
56
|
+
iqauthSub: string | null;
|
|
57
|
+
}
|
|
58
|
+
interface LinkAdapterTx {
|
|
59
|
+
findByIqAuthSub(sub: string): Promise<LinkCandidate | null>;
|
|
60
|
+
findByEmail(email: string, opts: {
|
|
61
|
+
caseInsensitive: boolean;
|
|
62
|
+
}): Promise<LinkCandidate[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Write the IQAuth `sub` onto the matched row. Implementations SHOULD make
|
|
65
|
+
* this a conditional update (`WHERE id=? AND iqauth_sub IS NULL OR iqauth_sub=?`)
|
|
66
|
+
* and return `false` if no row was updated — that signals a concurrent
|
|
67
|
+
* writer claimed the row first and the helper will surface a `conflict`.
|
|
68
|
+
* For backward-compatibility, returning `void` is treated as "succeeded".
|
|
69
|
+
*/
|
|
70
|
+
setIqAuthSub(userId: string, sub: string): Promise<void | boolean>;
|
|
71
|
+
}
|
|
72
|
+
interface LinkAdapter {
|
|
73
|
+
/**
|
|
74
|
+
* Run `fn` inside a serialized transaction. Implementations MUST guarantee
|
|
75
|
+
* that candidate rows surfaced by `findByIqAuthSub` / `findByEmail` are
|
|
76
|
+
* held under a row-level lock (or equivalent isolation) for the duration
|
|
77
|
+
* of the transaction so the read+write pair is atomic against other
|
|
78
|
+
* concurrent first-time logins for the same user. Concretely:
|
|
79
|
+
* - Postgres / MySQL: implement the read with `SELECT … FOR UPDATE`.
|
|
80
|
+
* - SQLite: relies on the file-level write lock — no extra clause needed.
|
|
81
|
+
* - Otherwise: run the transaction at SERIALIZABLE isolation.
|
|
82
|
+
* The reference `createDrizzleLinkAdapter` does this for you.
|
|
83
|
+
*/
|
|
84
|
+
withTransaction<T>(fn: (tx: LinkAdapterTx) => Promise<T>): Promise<T>;
|
|
85
|
+
}
|
|
86
|
+
interface LinkLocalUserOptions {
|
|
87
|
+
adapter: LinkAdapter;
|
|
88
|
+
claims: Pick<JwtClaims, "sub" | "email">;
|
|
89
|
+
/**
|
|
90
|
+
* Lookup keys to try, in order. Currently only `'email'` is supported.
|
|
91
|
+
* Defaults to `['email']`.
|
|
92
|
+
*/
|
|
93
|
+
lookupBy?: LinkLookupBy[];
|
|
94
|
+
/**
|
|
95
|
+
* Email matching. Default true — most legacy stores fold case in delivery
|
|
96
|
+
* but preserve the cased original at registration time.
|
|
97
|
+
*/
|
|
98
|
+
caseInsensitiveEmail?: boolean;
|
|
99
|
+
}
|
|
100
|
+
declare function linkLocalUserToIqAuthSub(options: LinkLocalUserOptions): Promise<LinkResult>;
|
|
101
|
+
interface DrizzleLikeDb {
|
|
102
|
+
transaction<T>(fn: (tx: unknown) => Promise<T>): Promise<T>;
|
|
103
|
+
}
|
|
104
|
+
interface DrizzleLinkAdapterDeps {
|
|
105
|
+
/** Drizzle DB instance with `.transaction()`. */
|
|
106
|
+
db: DrizzleLikeDb;
|
|
107
|
+
/** Drizzle table object (e.g. `users`). */
|
|
108
|
+
table: unknown;
|
|
109
|
+
/** Column refs on the table — `id`, `email`, and the iqauth sub column. */
|
|
110
|
+
columns: {
|
|
111
|
+
id: unknown;
|
|
112
|
+
email: unknown;
|
|
113
|
+
iqauthSub: unknown;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Optional override for the schema property keys used by `.set(...)` on
|
|
117
|
+
* UPDATE. Drizzle's `.set()` is keyed by the table's TypeScript property
|
|
118
|
+
* names (the keys of your schema object), which need not match the
|
|
119
|
+
* underlying column name. Defaults to `{ iqauthSub: 'iqauthSub' }`.
|
|
120
|
+
*/
|
|
121
|
+
columnNames?: {
|
|
122
|
+
iqauthSub?: string;
|
|
123
|
+
};
|
|
124
|
+
/** Drizzle `eq` operator import (`drizzle-orm`). */
|
|
125
|
+
eq: (a: unknown, b: unknown) => unknown;
|
|
126
|
+
/**
|
|
127
|
+
* Drizzle `sql` template tag (`drizzle-orm`). Used for case-insensitive
|
|
128
|
+
* email comparison via `lower(email) = lower($1)`.
|
|
129
|
+
*/
|
|
130
|
+
sql: (strings: TemplateStringsArray, ...values: unknown[]) => unknown;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Reference adapter for Drizzle ORM (Postgres / SQLite / MySQL). The host
|
|
134
|
+
* app passes its own `db`, `table`, column refs, and `eq` / `sql` imports
|
|
135
|
+
* so this SDK has no hard dependency on `drizzle-orm`.
|
|
136
|
+
*
|
|
137
|
+
* Example:
|
|
138
|
+
*
|
|
139
|
+
* import { eq, sql } from 'drizzle-orm';
|
|
140
|
+
* import { db } from './db';
|
|
141
|
+
* import { users } from './schema';
|
|
142
|
+
* import { createDrizzleLinkAdapter, linkLocalUserToIqAuthSub } from '@iqauth/sdk/server';
|
|
143
|
+
*
|
|
144
|
+
* const adapter = createDrizzleLinkAdapter({
|
|
145
|
+
* db, table: users, eq, sql,
|
|
146
|
+
* columns: { id: users.id, email: users.email, iqauthSub: users.iqauthSub },
|
|
147
|
+
* });
|
|
148
|
+
* const result = await linkLocalUserToIqAuthSub({ adapter, claims: req.auth });
|
|
149
|
+
*/
|
|
150
|
+
declare function createDrizzleLinkAdapter(deps: DrizzleLinkAdapterDeps): LinkAdapter;
|
|
8
151
|
|
|
9
152
|
declare class ServerIQAuthClient extends IQAuthClient {
|
|
10
153
|
constructor(config: IQAuthTokenClientConfig);
|
|
@@ -12,4 +155,4 @@ declare class ServerIQAuthClient extends IQAuthClient {
|
|
|
12
155
|
}
|
|
13
156
|
declare function createServerClient(config: IQAuthTokenClientConfig): ServerIQAuthClient;
|
|
14
157
|
|
|
15
|
-
export { ExpressMiddlewareOptions, IQAuthClient, IQAuthTokenClientConfig, ServerIQAuthClient, createServerClient };
|
|
158
|
+
export { type DrizzleLinkAdapterDeps, ExpressMiddlewareOptions, IQAuthClient, IQAuthTokenClientConfig, type LinkAdapter, type LinkAdapterTx, type LinkCandidate, type LinkLocalUserOptions, type LinkLookupBy, type LinkResult, ServerIQAuthClient, createDrizzleLinkAdapter, createServerClient, linkLocalUserToIqAuthSub };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,10 +1,153 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { I as IQAuthClient } from './client-
|
|
3
|
-
export { E as ErrorCodes, I as IQAuthError } from './errors-
|
|
4
|
-
export { C as CookieAwareMiddlewareOptions, D as DEFAULT_ACCESS_COOKIE, a as DEFAULT_REFRESH_COOKIE, i as iqAuthMiddleware } from './express-
|
|
5
|
-
export { HandlerResponse, IQAuthHelperConfig, SetCookieDirective, handleCallback, handleRefresh, handleSignout, serializeCookie } from './server/handlers.js';
|
|
6
|
-
export { P as ProvisioningBridge, a as ProvisioningBridgeOptions, d as ProvisioningContext, b as ProvisioningStorage, c as createProvisioningBridge } from './provisioningBridge-
|
|
7
|
-
import './tokens-
|
|
1
|
+
import { J as JwtClaims, f as IQAuthTokenClientConfig, X as ExpressMiddlewareOptions, a as IQAuthRequestLike, b as IQAuthResponseLike, c as IQAuthNextFunction } from './types-XOV9XPVi.js';
|
|
2
|
+
import { I as IQAuthClient } from './client-CDQ21LvW.js';
|
|
3
|
+
export { E as ErrorCodes, I as IQAuthError } from './errors-Jl1Jtm-6.js';
|
|
4
|
+
export { C as CookieAwareMiddlewareOptions, D as DEFAULT_ACCESS_COOKIE, a as DEFAULT_REFRESH_COOKIE, i as iqAuthMiddleware } from './express-Piv2WhWM.js';
|
|
5
|
+
export { HandlerResponse, IQAuthHelperConfig, SetCookieDirective, UserinfoResponse, buildUserinfoResponse, handleCallback, handleRefresh, handleSignout, handleUserinfo, serializeCookie } from './server/handlers.js';
|
|
6
|
+
export { P as ProvisioningBridge, a as ProvisioningBridgeOptions, d as ProvisioningContext, b as ProvisioningStorage, c as createProvisioningBridge } from './provisioningBridge-CGpMRie4.js';
|
|
7
|
+
import './tokens-Bqhmqq_R.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* linkLocalUserToIqAuthSub — first-time-migration helper.
|
|
11
|
+
*
|
|
12
|
+
* Use case: an app already had local users before adopting IQAuth. After a
|
|
13
|
+
* user signs in via IQAuth, the JWT's `sub` won't match any local row. The
|
|
14
|
+
* conventional self-heal is "look up by email, write the sub onto the matching
|
|
15
|
+
* row". Doing this safely requires:
|
|
16
|
+
*
|
|
17
|
+
* 1. An atomic transaction (find + write) so two concurrent first-time
|
|
18
|
+
* sign-ins for the same user can't both succeed.
|
|
19
|
+
* 2. Email matching that's case-insensitive by default (since email
|
|
20
|
+
* providers fold case in delivery).
|
|
21
|
+
* 3. A refusal to link when the candidate row already has a different
|
|
22
|
+
* non-null `iqauthSub` (i.e. that local row belongs to someone else).
|
|
23
|
+
* 4. A refusal to link when more than one local row matches the email
|
|
24
|
+
* (duplicate-email collision in the legacy table).
|
|
25
|
+
*
|
|
26
|
+
* The helper is ORM-agnostic. Pass any adapter that implements
|
|
27
|
+
* `LinkAdapter`. A reference Drizzle adapter is exported as
|
|
28
|
+
* `createDrizzleLinkAdapter`.
|
|
29
|
+
*
|
|
30
|
+
* Result codes:
|
|
31
|
+
* - 'linked' — wrote claims.sub onto the matched local row.
|
|
32
|
+
* - 'already_linked' — a row was already keyed by claims.sub. No write.
|
|
33
|
+
* - 'conflict' — matched row already has a different non-null sub,
|
|
34
|
+
* OR more than one local row matches the email.
|
|
35
|
+
* - 'not_found' — no local row matched any of the provided lookupBy
|
|
36
|
+
* keys. Caller should provision a new row separately.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
type LinkLookupBy = "email";
|
|
40
|
+
type LinkResult = {
|
|
41
|
+
status: "linked";
|
|
42
|
+
userId: string;
|
|
43
|
+
} | {
|
|
44
|
+
status: "already_linked";
|
|
45
|
+
userId: string;
|
|
46
|
+
} | {
|
|
47
|
+
status: "conflict";
|
|
48
|
+
userId?: string;
|
|
49
|
+
reason: "different_sub" | "duplicate_email";
|
|
50
|
+
} | {
|
|
51
|
+
status: "not_found";
|
|
52
|
+
};
|
|
53
|
+
interface LinkCandidate {
|
|
54
|
+
id: string;
|
|
55
|
+
email: string | null;
|
|
56
|
+
iqauthSub: string | null;
|
|
57
|
+
}
|
|
58
|
+
interface LinkAdapterTx {
|
|
59
|
+
findByIqAuthSub(sub: string): Promise<LinkCandidate | null>;
|
|
60
|
+
findByEmail(email: string, opts: {
|
|
61
|
+
caseInsensitive: boolean;
|
|
62
|
+
}): Promise<LinkCandidate[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Write the IQAuth `sub` onto the matched row. Implementations SHOULD make
|
|
65
|
+
* this a conditional update (`WHERE id=? AND iqauth_sub IS NULL OR iqauth_sub=?`)
|
|
66
|
+
* and return `false` if no row was updated — that signals a concurrent
|
|
67
|
+
* writer claimed the row first and the helper will surface a `conflict`.
|
|
68
|
+
* For backward-compatibility, returning `void` is treated as "succeeded".
|
|
69
|
+
*/
|
|
70
|
+
setIqAuthSub(userId: string, sub: string): Promise<void | boolean>;
|
|
71
|
+
}
|
|
72
|
+
interface LinkAdapter {
|
|
73
|
+
/**
|
|
74
|
+
* Run `fn` inside a serialized transaction. Implementations MUST guarantee
|
|
75
|
+
* that candidate rows surfaced by `findByIqAuthSub` / `findByEmail` are
|
|
76
|
+
* held under a row-level lock (or equivalent isolation) for the duration
|
|
77
|
+
* of the transaction so the read+write pair is atomic against other
|
|
78
|
+
* concurrent first-time logins for the same user. Concretely:
|
|
79
|
+
* - Postgres / MySQL: implement the read with `SELECT … FOR UPDATE`.
|
|
80
|
+
* - SQLite: relies on the file-level write lock — no extra clause needed.
|
|
81
|
+
* - Otherwise: run the transaction at SERIALIZABLE isolation.
|
|
82
|
+
* The reference `createDrizzleLinkAdapter` does this for you.
|
|
83
|
+
*/
|
|
84
|
+
withTransaction<T>(fn: (tx: LinkAdapterTx) => Promise<T>): Promise<T>;
|
|
85
|
+
}
|
|
86
|
+
interface LinkLocalUserOptions {
|
|
87
|
+
adapter: LinkAdapter;
|
|
88
|
+
claims: Pick<JwtClaims, "sub" | "email">;
|
|
89
|
+
/**
|
|
90
|
+
* Lookup keys to try, in order. Currently only `'email'` is supported.
|
|
91
|
+
* Defaults to `['email']`.
|
|
92
|
+
*/
|
|
93
|
+
lookupBy?: LinkLookupBy[];
|
|
94
|
+
/**
|
|
95
|
+
* Email matching. Default true — most legacy stores fold case in delivery
|
|
96
|
+
* but preserve the cased original at registration time.
|
|
97
|
+
*/
|
|
98
|
+
caseInsensitiveEmail?: boolean;
|
|
99
|
+
}
|
|
100
|
+
declare function linkLocalUserToIqAuthSub(options: LinkLocalUserOptions): Promise<LinkResult>;
|
|
101
|
+
interface DrizzleLikeDb {
|
|
102
|
+
transaction<T>(fn: (tx: unknown) => Promise<T>): Promise<T>;
|
|
103
|
+
}
|
|
104
|
+
interface DrizzleLinkAdapterDeps {
|
|
105
|
+
/** Drizzle DB instance with `.transaction()`. */
|
|
106
|
+
db: DrizzleLikeDb;
|
|
107
|
+
/** Drizzle table object (e.g. `users`). */
|
|
108
|
+
table: unknown;
|
|
109
|
+
/** Column refs on the table — `id`, `email`, and the iqauth sub column. */
|
|
110
|
+
columns: {
|
|
111
|
+
id: unknown;
|
|
112
|
+
email: unknown;
|
|
113
|
+
iqauthSub: unknown;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Optional override for the schema property keys used by `.set(...)` on
|
|
117
|
+
* UPDATE. Drizzle's `.set()` is keyed by the table's TypeScript property
|
|
118
|
+
* names (the keys of your schema object), which need not match the
|
|
119
|
+
* underlying column name. Defaults to `{ iqauthSub: 'iqauthSub' }`.
|
|
120
|
+
*/
|
|
121
|
+
columnNames?: {
|
|
122
|
+
iqauthSub?: string;
|
|
123
|
+
};
|
|
124
|
+
/** Drizzle `eq` operator import (`drizzle-orm`). */
|
|
125
|
+
eq: (a: unknown, b: unknown) => unknown;
|
|
126
|
+
/**
|
|
127
|
+
* Drizzle `sql` template tag (`drizzle-orm`). Used for case-insensitive
|
|
128
|
+
* email comparison via `lower(email) = lower($1)`.
|
|
129
|
+
*/
|
|
130
|
+
sql: (strings: TemplateStringsArray, ...values: unknown[]) => unknown;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Reference adapter for Drizzle ORM (Postgres / SQLite / MySQL). The host
|
|
134
|
+
* app passes its own `db`, `table`, column refs, and `eq` / `sql` imports
|
|
135
|
+
* so this SDK has no hard dependency on `drizzle-orm`.
|
|
136
|
+
*
|
|
137
|
+
* Example:
|
|
138
|
+
*
|
|
139
|
+
* import { eq, sql } from 'drizzle-orm';
|
|
140
|
+
* import { db } from './db';
|
|
141
|
+
* import { users } from './schema';
|
|
142
|
+
* import { createDrizzleLinkAdapter, linkLocalUserToIqAuthSub } from '@iqauth/sdk/server';
|
|
143
|
+
*
|
|
144
|
+
* const adapter = createDrizzleLinkAdapter({
|
|
145
|
+
* db, table: users, eq, sql,
|
|
146
|
+
* columns: { id: users.id, email: users.email, iqauthSub: users.iqauthSub },
|
|
147
|
+
* });
|
|
148
|
+
* const result = await linkLocalUserToIqAuthSub({ adapter, claims: req.auth });
|
|
149
|
+
*/
|
|
150
|
+
declare function createDrizzleLinkAdapter(deps: DrizzleLinkAdapterDeps): LinkAdapter;
|
|
8
151
|
|
|
9
152
|
declare class ServerIQAuthClient extends IQAuthClient {
|
|
10
153
|
constructor(config: IQAuthTokenClientConfig);
|
|
@@ -12,4 +155,4 @@ declare class ServerIQAuthClient extends IQAuthClient {
|
|
|
12
155
|
}
|
|
13
156
|
declare function createServerClient(config: IQAuthTokenClientConfig): ServerIQAuthClient;
|
|
14
157
|
|
|
15
|
-
export { ExpressMiddlewareOptions, IQAuthClient, IQAuthTokenClientConfig, ServerIQAuthClient, createServerClient };
|
|
158
|
+
export { type DrizzleLinkAdapterDeps, ExpressMiddlewareOptions, IQAuthClient, IQAuthTokenClientConfig, type LinkAdapter, type LinkAdapterTx, type LinkCandidate, type LinkLocalUserOptions, type LinkLookupBy, type LinkResult, ServerIQAuthClient, createDrizzleLinkAdapter, createServerClient, linkLocalUserToIqAuthSub };
|