@shaferllc/keel 0.59.0 → 0.66.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.
@@ -56,7 +56,7 @@ export class HasMany extends Relation {
56
56
  return this.parent[this.localKey];
57
57
  }
58
58
  query() {
59
- return db(this.related.table).where(this.foreignKey, this.localValue());
59
+ return db(this.related.table, this.related.connection).where(this.foreignKey, this.localValue());
60
60
  }
61
61
  async get() {
62
62
  return this.hydrate(await this.query().get());
@@ -64,7 +64,7 @@ export class HasMany extends Relation {
64
64
  async eager(models, name) {
65
65
  const keys = unique(models.map((m) => m[this.localKey]).filter((v) => v != null));
66
66
  const rows = keys.length
67
- ? await db(this.related.table).whereIn(this.foreignKey, keys).get()
67
+ ? await db(this.related.table, this.related.connection).whereIn(this.foreignKey, keys).get()
68
68
  : [];
69
69
  const grouped = new Map();
70
70
  for (const row of rows) {
@@ -90,7 +90,7 @@ export class HasOne extends Relation {
90
90
  return this.parent[this.localKey];
91
91
  }
92
92
  query() {
93
- return db(this.related.table).where(this.foreignKey, this.localValue());
93
+ return db(this.related.table, this.related.connection).where(this.foreignKey, this.localValue());
94
94
  }
95
95
  async get() {
96
96
  const row = await this.query().first();
@@ -99,7 +99,7 @@ export class HasOne extends Relation {
99
99
  async eager(models, name) {
100
100
  const keys = unique(models.map((m) => m[this.localKey]).filter((v) => v != null));
101
101
  const rows = keys.length
102
- ? await db(this.related.table).whereIn(this.foreignKey, keys).get()
102
+ ? await db(this.related.table, this.related.connection).whereIn(this.foreignKey, keys).get()
103
103
  : [];
104
104
  const byKey = new Map();
105
105
  for (const row of rows) {
@@ -124,7 +124,7 @@ export class BelongsTo extends Relation {
124
124
  return this.parent[this.foreignKey];
125
125
  }
126
126
  query() {
127
- return db(this.related.table).where(this.ownerKey, this.foreignValue());
127
+ return db(this.related.table, this.related.connection).where(this.ownerKey, this.foreignValue());
128
128
  }
129
129
  async get() {
130
130
  if (this.foreignValue() == null)
@@ -135,7 +135,7 @@ export class BelongsTo extends Relation {
135
135
  async eager(models, name) {
136
136
  const keys = unique(models.map((m) => m[this.foreignKey]).filter((v) => v != null));
137
137
  const rows = keys.length
138
- ? await db(this.related.table).whereIn(this.ownerKey, keys).get()
138
+ ? await db(this.related.table, this.related.connection).whereIn(this.ownerKey, keys).get()
139
139
  : [];
140
140
  const byKey = new Map();
141
141
  for (const row of rows)
@@ -165,18 +165,18 @@ export class BelongsToMany extends Relation {
165
165
  }
166
166
  /** The query against the related table, once pivot rows are known. */
167
167
  query() {
168
- return db(this.related.table);
168
+ return db(this.related.table, this.related.connection);
169
169
  }
170
170
  async get() {
171
171
  if (this.parentValue() == null)
172
172
  return [];
173
- const pivots = await db(this.pivotTable)
173
+ const pivots = await db(this.pivotTable, this.related.connection)
174
174
  .where(this.foreignPivotKey, this.parentValue())
175
175
  .get();
176
176
  const relatedIds = unique(pivots.map((p) => p[this.relatedPivotKey]));
177
177
  if (!relatedIds.length)
178
178
  return [];
179
- const rows = await db(this.related.table).whereIn(this.relatedKey, relatedIds).get();
179
+ const rows = await db(this.related.table, this.related.connection).whereIn(this.relatedKey, relatedIds).get();
180
180
  return this.hydrate(rows);
181
181
  }
182
182
  async eager(models, name) {
@@ -186,10 +186,10 @@ export class BelongsToMany extends Relation {
186
186
  m.setRelation(name, []);
187
187
  return;
188
188
  }
189
- const pivots = await db(this.pivotTable).whereIn(this.foreignPivotKey, parentIds).get();
189
+ const pivots = await db(this.pivotTable, this.related.connection).whereIn(this.foreignPivotKey, parentIds).get();
190
190
  const relatedIds = unique(pivots.map((p) => p[this.relatedPivotKey]));
191
191
  const rows = relatedIds.length
192
- ? await db(this.related.table).whereIn(this.relatedKey, relatedIds).get()
192
+ ? await db(this.related.table, this.related.connection).whereIn(this.relatedKey, relatedIds).get()
193
193
  : [];
194
194
  const relatedById = new Map(rows.map((row) => [row[this.relatedKey], row]));
195
195
  const grouped = new Map();
@@ -207,7 +207,7 @@ export class BelongsToMany extends Relation {
207
207
  }
208
208
  /** Attach a related row by linking it through the pivot table. */
209
209
  async attach(id, extra = {}) {
210
- await db(this.pivotTable).insert({
210
+ await db(this.pivotTable, this.related.connection).insert({
211
211
  [this.foreignPivotKey]: this.parentValue(),
212
212
  [this.relatedPivotKey]: id,
213
213
  ...extra,
@@ -215,7 +215,7 @@ export class BelongsToMany extends Relation {
215
215
  }
216
216
  /** Detach one related row (or all, when no id is given). */
217
217
  async detach(id) {
218
- let q = db(this.pivotTable).where(this.foreignPivotKey, this.parentValue());
218
+ let q = db(this.pivotTable, this.related.connection).where(this.foreignPivotKey, this.parentValue());
219
219
  if (id !== undefined)
220
220
  q = q.where(this.relatedPivotKey, id);
221
221
  await q.delete();
@@ -33,6 +33,8 @@ interface ResponseHelper {
33
33
  text(body: string, status?: number): Response;
34
34
  html(body: string, status?: number): Response;
35
35
  redirect(location: string, status?: number): Response;
36
+ /** Redirect to the `Referer` header, or `fallback` (default "/") if absent. */
37
+ back(fallback?: string, status?: number): Response;
36
38
  /** Send a value — objects become JSON, everything else becomes text. */
37
39
  send(data: unknown, status?: number): Response;
38
40
  /** Set the response status (chainable). */
@@ -47,6 +49,8 @@ interface ResponseHelper {
47
49
  hasHeader(name: string): boolean;
48
50
  /** Set the Content-Type (chainable). */
49
51
  type(mime: string): ResponseHelper;
52
+ /** Mark the response as a downloadable attachment via Content-Disposition (chainable). */
53
+ attachment(filename?: string): ResponseHelper;
50
54
  /** Append a value to a (possibly multi-value) header (chainable). */
51
55
  append(name: string, value: string): ResponseHelper;
52
56
  /** Remove a response header (chainable). */
@@ -74,6 +78,20 @@ export declare const request: {
74
78
  readonly method: string;
75
79
  readonly path: string;
76
80
  readonly url: string;
81
+ /** The request protocol — "https" or "http" — honoring X-Forwarded-Proto. */
82
+ readonly protocol: string;
83
+ /** Whether the request came in over HTTPS. */
84
+ readonly secure: boolean;
85
+ /** The host with port, honoring X-Forwarded-Host (e.g. "example.com:443"). */
86
+ readonly host: string;
87
+ /** The host without port (e.g. "example.com"). */
88
+ readonly hostname: string;
89
+ /** Scheme + host — "https://example.com" — with no trailing slash. */
90
+ readonly origin: string;
91
+ /** The absolute request URL, rebuilt from the (proxy-aware) origin. */
92
+ readonly fullUrl: string;
93
+ /** The raw query string without the leading "?" (empty when none). */
94
+ readonly querystring: string;
77
95
  /** The response status (useful after `await next()` in middleware). */
78
96
  readonly status: number;
79
97
  header(name: string): string | undefined;
@@ -128,6 +146,14 @@ export declare const request: {
128
146
  language(languages: string[]): string | null;
129
147
  /** Accepted languages, ordered by preference. */
130
148
  languages(): string[];
149
+ /** The best of the offered content encodings per Accept-Encoding, or null. */
150
+ encoding(encodings: string[]): string | null;
151
+ /** Accepted content encodings, ordered by preference. */
152
+ encodings(): string[];
153
+ /** The best of the offered charsets per Accept-Charset, or null. */
154
+ charset(charsets: string[]): string | null;
155
+ /** Accepted charsets, ordered by preference. */
156
+ charsets(): string[];
131
157
  /** A single input (from query or body), with an optional fallback (async). */
132
158
  input<T = unknown>(key: string, fallback?: T): Promise<T>;
133
159
  /** Only the named inputs (async). */
@@ -67,6 +67,20 @@ function negotiate(headerName, offered) {
67
67
  return a;
68
68
  return null;
69
69
  }
70
+ /* ------------------------------ url / host ----------------------------- */
71
+ /* Proxy-aware URL introspection: X-Forwarded-* wins over the raw URL, so an
72
+ * app behind a TLS-terminating proxy sees the client's protocol and host. */
73
+ /** The first value of a (possibly comma-listed) header. */
74
+ function firstForwarded(name) {
75
+ const v = ctx().req.header(name);
76
+ return v ? v.split(",")[0].trim() : undefined;
77
+ }
78
+ function requestProtocol() {
79
+ return firstForwarded("x-forwarded-proto") ?? new URL(ctx().req.url).protocol.replace(/:$/, "");
80
+ }
81
+ function requestHost() {
82
+ return firstForwarded("x-forwarded-host") ?? ctx().req.header("host") ?? new URL(ctx().req.url).host;
83
+ }
70
84
  /* ------------------------------ responses ------------------------------ */
71
85
  /* These work inside a handler AND standalone (e.g. as a static route value).
72
86
  * Inside a request they build on the context (merging any headers); outside a
@@ -98,6 +112,9 @@ export function html(body, status) {
98
112
  }
99
113
  export function redirect(location, status) {
100
114
  const c = maybeCtx();
115
+ // Koa-style `redirect("back")`: bounce to the Referer, or "/" if there isn't one.
116
+ if (location === "back")
117
+ location = c?.req.header("referer") ?? "/";
101
118
  return c
102
119
  ? c.redirect(location, status)
103
120
  : new Response(null, { status: status ?? 302, headers: { location } });
@@ -115,6 +132,9 @@ export const response = {
115
132
  redirect(location, status) {
116
133
  return redirect(location, status);
117
134
  },
135
+ back(fallback = "/", status) {
136
+ return redirect(ctx().req.header("referer") ?? fallback, status);
137
+ },
118
138
  send(data, status) {
119
139
  return typeof data === "object" && data !== null
120
140
  ? json(data, status)
@@ -143,6 +163,18 @@ export const response = {
143
163
  ctx().header("content-type", mime);
144
164
  return response;
145
165
  },
166
+ attachment(filename) {
167
+ if (filename === undefined) {
168
+ ctx().header("content-disposition", "attachment");
169
+ }
170
+ else {
171
+ // Quote the ASCII-safe name; add RFC 5987 filename* for anything else.
172
+ const ascii = filename.replace(/[^\x20-\x7e]/g, "?").replace(/["\\]/g, "_");
173
+ const encoded = encodeURIComponent(filename);
174
+ ctx().header("content-disposition", `attachment; filename="${ascii}"; filename*=UTF-8''${encoded}`);
175
+ }
176
+ return response;
177
+ },
146
178
  append(name, value) {
147
179
  ctx().header(name, value, { append: true });
148
180
  return response;
@@ -189,6 +221,35 @@ export const request = {
189
221
  get url() {
190
222
  return ctx().req.url;
191
223
  },
224
+ /** The request protocol — "https" or "http" — honoring X-Forwarded-Proto. */
225
+ get protocol() {
226
+ return requestProtocol();
227
+ },
228
+ /** Whether the request came in over HTTPS. */
229
+ get secure() {
230
+ return requestProtocol() === "https";
231
+ },
232
+ /** The host with port, honoring X-Forwarded-Host (e.g. "example.com:443"). */
233
+ get host() {
234
+ return requestHost();
235
+ },
236
+ /** The host without port (e.g. "example.com"). */
237
+ get hostname() {
238
+ return requestHost().split(":")[0];
239
+ },
240
+ /** Scheme + host — "https://example.com" — with no trailing slash. */
241
+ get origin() {
242
+ return `${requestProtocol()}://${requestHost()}`;
243
+ },
244
+ /** The absolute request URL, rebuilt from the (proxy-aware) origin. */
245
+ get fullUrl() {
246
+ const u = new URL(ctx().req.url);
247
+ return `${requestProtocol()}://${requestHost()}${u.pathname}${u.search}`;
248
+ },
249
+ /** The raw query string without the leading "?" (empty when none). */
250
+ get querystring() {
251
+ return new URL(ctx().req.url).search.replace(/^\?/, "");
252
+ },
192
253
  /** The response status (useful after `await next()` in middleware). */
193
254
  get status() {
194
255
  return ctx().res.status;
@@ -332,6 +393,22 @@ export const request = {
332
393
  languages() {
333
394
  return parseAccept(ctx().req.header("accept-language"));
334
395
  },
396
+ /** The best of the offered content encodings per Accept-Encoding, or null. */
397
+ encoding(encodings) {
398
+ return negotiate("accept-encoding", encodings);
399
+ },
400
+ /** Accepted content encodings, ordered by preference. */
401
+ encodings() {
402
+ return parseAccept(ctx().req.header("accept-encoding"));
403
+ },
404
+ /** The best of the offered charsets per Accept-Charset, or null. */
405
+ charset(charsets) {
406
+ return negotiate("accept-charset", charsets);
407
+ },
408
+ /** Accepted charsets, ordered by preference. */
409
+ charsets() {
410
+ return parseAccept(ctx().req.header("accept-charset"));
411
+ },
335
412
  /** A single input (from query or body), with an optional fallback (async). */
336
413
  async input(key, fallback) {
337
414
  const all = await this.all();
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Security headers — the "shield" for server-rendered apps. One middleware that
3
+ * sets the defensive HTTP headers browsers act on: a Content-Security-Policy,
4
+ * HSTS, clickjacking and MIME-sniffing guards, and a referrer policy.
5
+ *
6
+ * this.use(securityHeaders()); // sensible defaults
7
+ *
8
+ * this.use(securityHeaders({
9
+ * csp: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "https://cdn.example.com"] },
10
+ * hsts: { maxAge: 15552000, includeSubDomains: true },
11
+ * frameGuard: "DENY",
12
+ * }));
13
+ *
14
+ * Each header can be turned off with `false`. CSP takes a ready-made string or a
15
+ * directives object whose camelCase keys become `kebab-case` (`defaultSrc` →
16
+ * `default-src`). Pair with [`csrf()`](./csrf.ts) for form protection.
17
+ */
18
+ import type { MiddlewareHandler } from "hono";
19
+ export interface HstsOptions {
20
+ /** Max-age in seconds. Default 180 days. */
21
+ maxAge?: number;
22
+ /** Apply to subdomains too. Default true. */
23
+ includeSubDomains?: boolean;
24
+ /** Add `preload` (only if you've submitted to the HSTS preload list). Default false. */
25
+ preload?: boolean;
26
+ }
27
+ export interface SecurityHeadersOptions {
28
+ /** Content-Security-Policy — a raw string, a directives object, or `false` to omit. */
29
+ csp?: string | Record<string, string[]> | false;
30
+ /** Strict-Transport-Security. `true` for defaults, an object to tune, `false` to omit. Default off unless set. */
31
+ hsts?: boolean | HstsOptions;
32
+ /** X-Frame-Options clickjacking guard. Default `"SAMEORIGIN"`; `false` to omit. */
33
+ frameGuard?: false | "DENY" | "SAMEORIGIN";
34
+ /** X-Content-Type-Options: nosniff. Default true. */
35
+ noSniff?: boolean;
36
+ /** Referrer-Policy. Default `"strict-origin-when-cross-origin"`; `false` to omit. */
37
+ referrerPolicy?: string | false;
38
+ }
39
+ export declare function securityHeaders(options?: SecurityHeadersOptions): MiddlewareHandler;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Security headers — the "shield" for server-rendered apps. One middleware that
3
+ * sets the defensive HTTP headers browsers act on: a Content-Security-Policy,
4
+ * HSTS, clickjacking and MIME-sniffing guards, and a referrer policy.
5
+ *
6
+ * this.use(securityHeaders()); // sensible defaults
7
+ *
8
+ * this.use(securityHeaders({
9
+ * csp: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "https://cdn.example.com"] },
10
+ * hsts: { maxAge: 15552000, includeSubDomains: true },
11
+ * frameGuard: "DENY",
12
+ * }));
13
+ *
14
+ * Each header can be turned off with `false`. CSP takes a ready-made string or a
15
+ * directives object whose camelCase keys become `kebab-case` (`defaultSrc` →
16
+ * `default-src`). Pair with [`csrf()`](./csrf.ts) for form protection.
17
+ */
18
+ /** camelCase directive names → the kebab-case CSP spelling. */
19
+ function buildCsp(directives) {
20
+ return Object.entries(directives)
21
+ .map(([key, values]) => {
22
+ const name = key.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
23
+ return values.length ? `${name} ${values.join(" ")}` : name;
24
+ })
25
+ .join("; ");
26
+ }
27
+ export function securityHeaders(options = {}) {
28
+ const frame = options.frameGuard === undefined ? "SAMEORIGIN" : options.frameGuard;
29
+ const referrer = options.referrerPolicy === undefined ? "strict-origin-when-cross-origin" : options.referrerPolicy;
30
+ const noSniff = options.noSniff !== false;
31
+ // Precompute the static header values once.
32
+ const csp = options.csp === false || options.csp === undefined
33
+ ? null
34
+ : typeof options.csp === "string"
35
+ ? options.csp
36
+ : buildCsp(options.csp);
37
+ let hstsValue = null;
38
+ if (options.hsts) {
39
+ const h = options.hsts === true ? {} : options.hsts;
40
+ const parts = [`max-age=${h.maxAge ?? 15552000}`];
41
+ if (h.includeSubDomains !== false)
42
+ parts.push("includeSubDomains");
43
+ if (h.preload)
44
+ parts.push("preload");
45
+ hstsValue = parts.join("; ");
46
+ }
47
+ return async (c, next) => {
48
+ await next();
49
+ if (csp)
50
+ c.header("Content-Security-Policy", csp);
51
+ if (hstsValue)
52
+ c.header("Strict-Transport-Security", hstsValue);
53
+ if (frame)
54
+ c.header("X-Frame-Options", frame);
55
+ if (noSniff)
56
+ c.header("X-Content-Type-Options", "nosniff");
57
+ if (referrer)
58
+ c.header("Referrer-Policy", referrer);
59
+ };
60
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Social authentication — OAuth 2.0 "sign in with GitHub/Google/…". Like Adonis
3
+ * Ally, this owns the OAuth dance only: it hands you a normalized `SocialUser`,
4
+ * and *you* find-or-create your own user and log them in (with a session,
5
+ * `jwt`, or an access `token`). It stores nothing.
6
+ *
7
+ * const github = social.github({ clientId, clientSecret, redirectUri });
8
+ *
9
+ * // 1. send the user off to the provider
10
+ * router.get("/auth/github", () => redirect(github.redirect({ state })));
11
+ *
12
+ * // 2. handle the callback
13
+ * router.get("/auth/github/callback", async () => {
14
+ * const gh = await github.user(request.query("code")); // { id, email, name, … }
15
+ * const user = await users.firstOrCreate({ github_id: gh.id }, { email: gh.email });
16
+ * auth().login(user.id);
17
+ * });
18
+ *
19
+ * Every driver is `fetch`-based — no SDK, no native deps — so it runs on Node and
20
+ * the edge alike. Presets cover GitHub, Google, and Discord; build your own with
21
+ * `oauthDriver()` for anything else OAuth2.
22
+ */
23
+ /** An OAuth token set returned by the provider's token endpoint. */
24
+ export interface OAuthToken {
25
+ accessToken: string;
26
+ tokenType?: string;
27
+ refreshToken?: string;
28
+ /** Seconds until the access token expires, if the provider says. */
29
+ expiresIn?: number;
30
+ scope?: string;
31
+ /** The raw token response, for provider-specific fields. */
32
+ raw: Record<string, unknown>;
33
+ }
34
+ /**
35
+ * A provider's user, normalized to a common shape across every driver. `Token`
36
+ * is the OAuth2 `OAuthToken` by default, or an `OAuth1Token` for OAuth 1.0a
37
+ * providers.
38
+ */
39
+ export interface SocialUser<Token = OAuthToken> {
40
+ /** The provider's stable id for this user (always a string). */
41
+ id: string;
42
+ email: string | null;
43
+ name: string | null;
44
+ /** Username / handle (e.g. GitHub login, Discord username). */
45
+ nickname: string | null;
46
+ avatarUrl: string | null;
47
+ /** The token used to fetch this profile — for calling the provider's API. */
48
+ token: Token;
49
+ /** The raw provider profile, for fields not in the normalized shape. */
50
+ raw: Record<string, unknown>;
51
+ }
52
+ export interface OAuthConfig {
53
+ clientId: string;
54
+ clientSecret: string;
55
+ /** The callback URL registered with the provider. */
56
+ redirectUri: string;
57
+ /** Override the provider's default scopes. */
58
+ scopes?: string[];
59
+ }
60
+ export interface RedirectOptions {
61
+ /** A CSRF `state` value — generate with `oauthState()`, stash it, verify on callback. */
62
+ state?: string;
63
+ /** Scopes for this redirect (overrides config + provider defaults). */
64
+ scopes?: string[];
65
+ /** Extra query parameters to add to the authorize URL (e.g. `prompt`, `access_type`). */
66
+ params?: Record<string, string>;
67
+ }
68
+ /** The provider-specific bits an `OAuthDriver` needs. */
69
+ export interface ProviderSpec {
70
+ name: string;
71
+ authorizeUrl: string;
72
+ tokenUrl: string;
73
+ defaultScopes: string[];
74
+ /** How scopes are joined in the URL — space for most, comma for a few. */
75
+ scopeSeparator?: string;
76
+ /** Fetch and normalize the provider's user for an access token. */
77
+ fetchUser(token: OAuthToken): Promise<SocialUser>;
78
+ }
79
+ /** Thrown when the token exchange or profile fetch fails. */
80
+ export declare class OAuthError extends Error {
81
+ readonly provider: string;
82
+ constructor(message: string, provider: string);
83
+ }
84
+ /** A random, URL-safe `state` for CSRF protection — stash it, then verify on callback. */
85
+ export declare function oauthState(bytes?: number): string;
86
+ /** A generic OAuth 2.0 authorization-code driver. */
87
+ export declare class OAuthDriver {
88
+ private spec;
89
+ private config;
90
+ constructor(spec: ProviderSpec, config: OAuthConfig);
91
+ /** Build the provider's authorize URL to redirect the user to. */
92
+ redirect(options?: RedirectOptions): string;
93
+ /** Exchange an authorization `code` (from the callback) for an access token. */
94
+ exchangeCode(code: string): Promise<OAuthToken>;
95
+ /** Fetch the normalized user for an already-obtained access token. */
96
+ userFromToken(token: OAuthToken): Promise<SocialUser>;
97
+ /** The full callback step: exchange the `code`, then fetch the user. */
98
+ user(code: string): Promise<SocialUser>;
99
+ }
100
+ /** Build a driver for any OAuth2 provider from a spec + config. */
101
+ export declare function oauthDriver(spec: ProviderSpec, config: OAuthConfig): OAuthDriver;
102
+ /** GitHub OAuth (`user:email` gives access to a verified primary email). */
103
+ export declare function github(config: OAuthConfig): OAuthDriver;
104
+ /** Google OAuth / OpenID Connect. */
105
+ export declare function google(config: OAuthConfig): OAuthDriver;
106
+ /** Discord OAuth. */
107
+ export declare function discord(config: OAuthConfig): OAuthDriver;
108
+ export interface OAuth1Config {
109
+ /** Consumer (API) key. */
110
+ clientId: string;
111
+ /** Consumer (API) secret. */
112
+ clientSecret: string;
113
+ /** The `oauth_callback` URL registered with the provider. */
114
+ redirectUri: string;
115
+ }
116
+ /** An OAuth 1.0a token pair — both the request token and the final access token. */
117
+ export interface OAuth1Token {
118
+ token: string;
119
+ tokenSecret: string;
120
+ raw: Record<string, string>;
121
+ }
122
+ export interface OAuth1ProviderSpec {
123
+ name: string;
124
+ requestTokenUrl: string;
125
+ authorizeUrl: string;
126
+ accessTokenUrl: string;
127
+ fetchUser(token: OAuth1Token, driver: OAuth1Driver): Promise<SocialUser<OAuth1Token>>;
128
+ }
129
+ /**
130
+ * Compute an OAuth 1.0a HMAC-SHA1 signature (RFC 5849). `params` holds every
131
+ * signed parameter with *raw* (unencoded) values — the oauth_* fields plus any
132
+ * query/body params, minus `oauth_signature`. Exposed for signing custom API
133
+ * requests beyond the built-in flow.
134
+ */
135
+ export declare function oauth1Signature(input: {
136
+ method: string;
137
+ url: string;
138
+ params: Record<string, string>;
139
+ consumerSecret: string;
140
+ tokenSecret?: string;
141
+ }): Promise<string>;
142
+ /** A generic OAuth 1.0a driver. */
143
+ export declare class OAuth1Driver {
144
+ private spec;
145
+ private config;
146
+ constructor(spec: OAuth1ProviderSpec, config: OAuth1Config);
147
+ /** Sign a request and build its `Authorization: OAuth …` header. */
148
+ private authHeader;
149
+ /** Step 1 — obtain a temporary request token. Stash its `tokenSecret` for the callback. */
150
+ requestToken(): Promise<OAuth1Token>;
151
+ /** Step 2 — the URL to send the user to, carrying the request token. */
152
+ redirect(requestToken: string | OAuth1Token): string;
153
+ /** Step 3 — swap the callback's `oauth_token` + `oauth_verifier` for an access token. */
154
+ accessToken(oauthToken: string, verifier: string, requestTokenSecret: string): Promise<OAuth1Token>;
155
+ /** The full callback step: exchange for an access token, then fetch the user. */
156
+ user(oauthToken: string, verifier: string, requestTokenSecret: string): Promise<SocialUser<OAuth1Token>>;
157
+ /** A signed GET against the provider's API on the user's behalf (for `fetchUser`). */
158
+ get(url: string, token: OAuth1Token): Promise<Record<string, unknown>>;
159
+ }
160
+ /** Build a driver for any OAuth 1.0a provider from a spec + config. */
161
+ export declare function oauth1Driver(spec: OAuth1ProviderSpec, config: OAuth1Config): OAuth1Driver;
162
+ /** Twitter / X (OAuth 1.0a). Enable "Request email" in your app settings for `email`. */
163
+ export declare function twitter(config: OAuth1Config): OAuth1Driver;
164
+ /** All social providers under one namespace: `social.github({...})`, `social.twitter({...})`. */
165
+ export declare const social: {
166
+ github: typeof github;
167
+ google: typeof google;
168
+ discord: typeof discord;
169
+ driver: typeof oauthDriver;
170
+ state: typeof oauthState;
171
+ twitter: typeof twitter;
172
+ driver1: typeof oauth1Driver;
173
+ };