@hadoku/worker-utils 0.0.0-stage → 1.6.20
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/LICENSE +15 -0
- package/README.md +334 -2
- package/dist/auth.d.ts +153 -0
- package/dist/auth.js +39 -0
- package/dist/authPage.d.ts +113 -0
- package/dist/authPage.js +833 -0
- package/dist/cloudflareWorkerLogger.d.ts +71 -0
- package/dist/cloudflareWorkerLogger.js +94 -0
- package/dist/content-level.d.ts +52 -0
- package/dist/content-level.js +28 -0
- package/dist/context.d.ts +110 -0
- package/dist/context.js +75 -0
- package/dist/cors.d.ts +123 -0
- package/dist/cors.js +113 -0
- package/dist/edge-routes.d.ts +47 -0
- package/dist/edge-routes.js +34 -0
- package/dist/identity.d.ts +477 -0
- package/dist/identity.js +239 -0
- package/dist/index.d.ts +108 -0
- package/dist/index.js +976 -0
- package/dist/masking.d.ts +108 -0
- package/dist/masking.js +90 -0
- package/dist/openapi-config.d.ts +252 -0
- package/dist/openapi-config.js +131 -0
- package/dist/openapi-schemas.d.ts +157 -0
- package/dist/openapi-schemas.js +79 -0
- package/dist/rate-limit.d.ts +39 -0
- package/dist/rate-limit.js +59 -0
- package/dist/responses.d.ts +159 -0
- package/dist/responses.js +80 -0
- package/dist/types.d.ts +166 -0
- package/dist/types.js +0 -0
- package/dist/validation.d.ts +88 -0
- package/dist/validation.js +172 -0
- package/package.json +34 -4
package/dist/identity.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// identity.ts
|
|
2
|
+
function isIdentityError(result) {
|
|
3
|
+
return "error" in result;
|
|
4
|
+
}
|
|
5
|
+
var NO_USER_ID = {
|
|
6
|
+
error: "That key has never signed in, so it has no id yet.",
|
|
7
|
+
status: 409,
|
|
8
|
+
code: "NO_USER_ID"
|
|
9
|
+
};
|
|
10
|
+
var NO_REGISTRY = {
|
|
11
|
+
error: "Identity resolution is unavailable: this worker has no key registry.",
|
|
12
|
+
status: 503,
|
|
13
|
+
code: "NO_REGISTRY"
|
|
14
|
+
};
|
|
15
|
+
var KEY_PREFIX = "key:";
|
|
16
|
+
function parseRegistryRow(raw) {
|
|
17
|
+
if (!raw) return null;
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(raw);
|
|
20
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
21
|
+
return parsed;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
var REGISTRY_METADATA_MAX_BYTES = 1024;
|
|
27
|
+
function registryRowMetadata(record) {
|
|
28
|
+
const meta = {
|
|
29
|
+
v: 1,
|
|
30
|
+
name: typeof record.name === "string" ? record.name : null
|
|
31
|
+
};
|
|
32
|
+
if (typeof record.tier === "string") meta.tier = record.tier;
|
|
33
|
+
if (typeof record.userId === "string") meta.userId = record.userId;
|
|
34
|
+
if (typeof record.createdAt === "number") meta.createdAt = record.createdAt;
|
|
35
|
+
if (typeof record.lastSeenAt === "number") meta.lastSeenAt = record.lastSeenAt;
|
|
36
|
+
if (typeof record.retiredAt === "number") meta.retiredAt = record.retiredAt;
|
|
37
|
+
if (typeof record.repo === "string" || record.repo === null) meta.repo = record.repo;
|
|
38
|
+
if (record.internal === true) meta.internal = true;
|
|
39
|
+
const bytes = new TextEncoder().encode(JSON.stringify(meta)).length;
|
|
40
|
+
return bytes <= REGISTRY_METADATA_MAX_BYTES ? meta : void 0;
|
|
41
|
+
}
|
|
42
|
+
function recordFromRegistryMetadata(raw) {
|
|
43
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
44
|
+
const m = raw;
|
|
45
|
+
if (m.v !== 1) return null;
|
|
46
|
+
if (m.name !== null && typeof m.name !== "string") return null;
|
|
47
|
+
const out = { name: m.name };
|
|
48
|
+
const str = (k) => m[k] === void 0 || typeof m[k] === "string";
|
|
49
|
+
const num = (k) => m[k] === void 0 || typeof m[k] === "number";
|
|
50
|
+
if (!str("tier") || !str("userId")) return null;
|
|
51
|
+
if (!num("createdAt") || !num("lastSeenAt") || !num("retiredAt")) return null;
|
|
52
|
+
if (m.repo !== void 0 && m.repo !== null && typeof m.repo !== "string") return null;
|
|
53
|
+
if (m.internal !== void 0 && m.internal !== true) return null;
|
|
54
|
+
if (m.tier !== void 0) out.tier = m.tier;
|
|
55
|
+
if (m.userId !== void 0) out.userId = m.userId;
|
|
56
|
+
if (m.createdAt !== void 0) out.createdAt = m.createdAt;
|
|
57
|
+
if (m.lastSeenAt !== void 0) out.lastSeenAt = m.lastSeenAt;
|
|
58
|
+
if (m.retiredAt !== void 0) out.retiredAt = m.retiredAt;
|
|
59
|
+
if (m.repo !== void 0) out.repo = m.repo;
|
|
60
|
+
if (m.internal === true) out.internal = true;
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
function registryKv(env) {
|
|
64
|
+
const kv = env.SESSIONS_KV;
|
|
65
|
+
if (!kv || typeof kv.list !== "function" || typeof kv.get !== "function") return null;
|
|
66
|
+
return kv;
|
|
67
|
+
}
|
|
68
|
+
function foldName(name) {
|
|
69
|
+
return name.trim().toLowerCase();
|
|
70
|
+
}
|
|
71
|
+
async function scanRegistry(env) {
|
|
72
|
+
const kv = registryKv(env);
|
|
73
|
+
if (!kv) return [];
|
|
74
|
+
const entries = [];
|
|
75
|
+
let cursor;
|
|
76
|
+
for (; ; ) {
|
|
77
|
+
const page = await kv.list({ prefix: KEY_PREFIX, ...cursor ? { cursor } : {} });
|
|
78
|
+
for (const e of page.keys) entries.push(e);
|
|
79
|
+
if (page.list_complete) break;
|
|
80
|
+
cursor = page.cursor;
|
|
81
|
+
if (!cursor) break;
|
|
82
|
+
}
|
|
83
|
+
const rows = await Promise.all(
|
|
84
|
+
entries.map(async (e) => ({
|
|
85
|
+
entry: e.name,
|
|
86
|
+
record: recordFromRegistryMetadata(e.metadata) ?? parseRegistryRow(await kv.get(e.name))
|
|
87
|
+
}))
|
|
88
|
+
);
|
|
89
|
+
return rows.filter(
|
|
90
|
+
(r) => r.record !== null
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
async function readLiveRows(env) {
|
|
94
|
+
const rows = [];
|
|
95
|
+
for (const { record } of await scanRegistry(env)) {
|
|
96
|
+
if (record.retiredAt || !record.userId) continue;
|
|
97
|
+
rows.push({ userId: record.userId, name: record.name ?? null, tier: record.tier });
|
|
98
|
+
}
|
|
99
|
+
return rows;
|
|
100
|
+
}
|
|
101
|
+
async function isRegistryNameTaken(env, name, exclude) {
|
|
102
|
+
const target = foldName(name);
|
|
103
|
+
if (!target) return false;
|
|
104
|
+
const excludeEntry = `${KEY_PREFIX}${exclude.rawKey}`;
|
|
105
|
+
for (const { entry, record } of await scanRegistry(env)) {
|
|
106
|
+
if (entry === excludeEntry) continue;
|
|
107
|
+
if (record.retiredAt) continue;
|
|
108
|
+
if (exclude.userId && record.userId === exclude.userId) continue;
|
|
109
|
+
if (record.name && foldName(record.name) === target) return true;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
function answersToName(tier) {
|
|
114
|
+
return tier !== MEMBER_TIER;
|
|
115
|
+
}
|
|
116
|
+
var MEMBER_TIER = "member";
|
|
117
|
+
async function resolveRegistryName(env, name) {
|
|
118
|
+
if (!registryKv(env)) return null;
|
|
119
|
+
const target = foldName(name);
|
|
120
|
+
if (!target) return null;
|
|
121
|
+
for (const { record } of await scanRegistry(env)) {
|
|
122
|
+
if (record.retiredAt || !answersToName(record.tier)) continue;
|
|
123
|
+
if (record.name && foldName(record.name) === target) {
|
|
124
|
+
return { name: record.name, userId: record.userId ?? null, tier: record.tier };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
async function liveRowsByName(env) {
|
|
130
|
+
const map = /* @__PURE__ */ new Map();
|
|
131
|
+
for (const row of await readLiveRows(env)) {
|
|
132
|
+
if (row.name && answersToName(row.tier)) map.set(foldName(row.name), row);
|
|
133
|
+
}
|
|
134
|
+
return map;
|
|
135
|
+
}
|
|
136
|
+
async function registryNameMap(env) {
|
|
137
|
+
const map = /* @__PURE__ */ new Map();
|
|
138
|
+
for (const row of await readLiveRows(env)) {
|
|
139
|
+
map.set(row.userId, { name: row.name, tier: row.tier });
|
|
140
|
+
}
|
|
141
|
+
return map;
|
|
142
|
+
}
|
|
143
|
+
var MIN_SEARCH_QUERY_LENGTH = 2;
|
|
144
|
+
async function searchRegistryNames(env, query, limit) {
|
|
145
|
+
const q = foldName(query);
|
|
146
|
+
if (q.length < MIN_SEARCH_QUERY_LENGTH) return [];
|
|
147
|
+
const seen = /* @__PURE__ */ new Set();
|
|
148
|
+
const out = [];
|
|
149
|
+
for (const row of await readLiveRows(env)) {
|
|
150
|
+
if (!row.name || !answersToName(row.tier)) continue;
|
|
151
|
+
const lower = foldName(row.name);
|
|
152
|
+
if (!lower.includes(q) || seen.has(lower)) continue;
|
|
153
|
+
seen.add(lower);
|
|
154
|
+
out.push({ name: row.name, tier: row.tier });
|
|
155
|
+
}
|
|
156
|
+
out.sort((a, b) => {
|
|
157
|
+
const ap = foldName(a.name).startsWith(q) ? 0 : 1;
|
|
158
|
+
const bp = foldName(b.name).startsWith(q) ? 0 : 1;
|
|
159
|
+
return ap - bp || a.name.localeCompare(b.name);
|
|
160
|
+
});
|
|
161
|
+
return out.slice(0, Math.max(0, limit));
|
|
162
|
+
}
|
|
163
|
+
async function resolveGranteeVia(edge, input) {
|
|
164
|
+
if (!edge || typeof edge.fetch !== "function" || !input.serviceKey) return NO_REGISTRY;
|
|
165
|
+
if (!input.name.trim()) return { error: "`name` is empty.", status: 400 };
|
|
166
|
+
const url = `https://hadoku.me/session/identity/resolve?name=${encodeURIComponent(input.name)}`;
|
|
167
|
+
let res;
|
|
168
|
+
try {
|
|
169
|
+
res = await edge.fetch(new Request(url, { headers: { "X-User-Key": input.serviceKey } }));
|
|
170
|
+
} catch {
|
|
171
|
+
return NO_REGISTRY;
|
|
172
|
+
}
|
|
173
|
+
if (res.status === 401 || res.status === 403) {
|
|
174
|
+
return NO_REGISTRY;
|
|
175
|
+
}
|
|
176
|
+
let body;
|
|
177
|
+
try {
|
|
178
|
+
body = await res.json();
|
|
179
|
+
} catch {
|
|
180
|
+
return NO_REGISTRY;
|
|
181
|
+
}
|
|
182
|
+
if (!body || typeof body !== "object") return NO_REGISTRY;
|
|
183
|
+
if (res.ok) {
|
|
184
|
+
const row = body;
|
|
185
|
+
if (typeof row.userId !== "string" || !row.userId) return NO_REGISTRY;
|
|
186
|
+
return { userId: row.userId, name: row.name ?? null, tier: row.tier };
|
|
187
|
+
}
|
|
188
|
+
const err = body;
|
|
189
|
+
const status = res.status === 404 ? 404 : res.status === 409 ? 409 : res.status === 503 ? 503 : 400;
|
|
190
|
+
return {
|
|
191
|
+
error: typeof err.error === "string" ? err.error : "Identity resolution failed.",
|
|
192
|
+
status,
|
|
193
|
+
...typeof err.code === "string" ? { code: err.code } : {}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
async function resolveGrantee(env, input) {
|
|
197
|
+
if (input.name) {
|
|
198
|
+
if (!registryKv(env)) {
|
|
199
|
+
return NO_REGISTRY;
|
|
200
|
+
}
|
|
201
|
+
if (!input.name.trim()) return { error: "`name` is empty.", status: 400 };
|
|
202
|
+
const row = await resolveRegistryName(env, input.name);
|
|
203
|
+
if (!row) {
|
|
204
|
+
return {
|
|
205
|
+
error: `No registered key named "${input.name}".`,
|
|
206
|
+
status: 404,
|
|
207
|
+
code: "NAME_NOT_FOUND"
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (!row.userId) return NO_USER_ID;
|
|
211
|
+
return { userId: row.userId, name: row.name, tier: row.tier };
|
|
212
|
+
}
|
|
213
|
+
const key = input.key;
|
|
214
|
+
if (!key) return { error: "Provide `name` or `key`.", status: 400 };
|
|
215
|
+
const kv = registryKv(env);
|
|
216
|
+
if (!kv) return NO_REGISTRY;
|
|
217
|
+
const rec = parseRegistryRow(await kv.get(`${KEY_PREFIX}${key}`));
|
|
218
|
+
if (!rec) return { error: "That key is not registered.", status: 400 };
|
|
219
|
+
if (!rec.userId) return NO_USER_ID;
|
|
220
|
+
return { userId: rec.userId, name: rec.name ?? null, tier: rec.tier };
|
|
221
|
+
}
|
|
222
|
+
export {
|
|
223
|
+
MIN_SEARCH_QUERY_LENGTH,
|
|
224
|
+
NO_REGISTRY,
|
|
225
|
+
NO_USER_ID,
|
|
226
|
+
REGISTRY_METADATA_MAX_BYTES,
|
|
227
|
+
answersToName,
|
|
228
|
+
isIdentityError,
|
|
229
|
+
isRegistryNameTaken,
|
|
230
|
+
liveRowsByName,
|
|
231
|
+
readLiveRows,
|
|
232
|
+
recordFromRegistryMetadata,
|
|
233
|
+
registryNameMap,
|
|
234
|
+
registryRowMetadata,
|
|
235
|
+
resolveGrantee,
|
|
236
|
+
resolveGranteeVia,
|
|
237
|
+
resolveRegistryName,
|
|
238
|
+
searchRegistryNames
|
|
239
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { HadokuAuthContext, HadokuAuthOptions } from './auth.js';
|
|
2
|
+
export { TIER_RANK, Tier, getAuthContext, requireMinTier, tierAtLeast } from './auth.js';
|
|
3
|
+
export { DEFAULT_CONTENT_LEVEL, MAX_CONTENT_LEVEL_BY_TIER, maxContentLevelForTier } from './content-level.js';
|
|
4
|
+
import { MiddlewareHandler, Context } from 'hono';
|
|
5
|
+
export { extractContext, extractField, getRequestMetadata, parseBody } from './context.js';
|
|
6
|
+
export { CommonRules, isNonEmptyString, requireFields, validateFields } from './validation.js';
|
|
7
|
+
export { CORSPresets, DEFAULT_HADOKU_ORIGINS, createCorsMiddleware, createHadokuCors, getCorsHeaders, isOriginAllowed, matchOrigin } from './cors.js';
|
|
8
|
+
export { SENSITIVE_FIELDS, createLogger, createRequestLogger, formatError, logError, logRequest, loggerMiddleware, redactFields, startTimer } from './cloudflareWorkerLogger.js';
|
|
9
|
+
export { RateLimitConfig, RateLimitResult, checkRateLimit, recordRequest, resetRateLimit } from './rate-limit.js';
|
|
10
|
+
export { MASKING, maskEmail, maskIpAddress, maskKey, maskSessionId } from './masking.js';
|
|
11
|
+
export { badRequest, created, healthCheck, notFound, ok, serverError, validationError } from './responses.js';
|
|
12
|
+
export { AuthConfig, AuthContext, CORSConfig, ContextExtractionConfig, ErrorResponse, HealthCheckResponse, LogEntry, LogLevel, LoggerConfig, SuccessResponse, UserType, ValidationResult, ValidationRule } from './types.js';
|
|
13
|
+
export { DetailedErrorResponseSchema, ErrorResponseSchema, PromptApiHealthResponseSchema, SimpleErrorResponseSchema, TaskApiHealthResponseSchema, ThrottleErrorResponseSchema, ValidationErrorResponseSchema, createDeleteResponseSchema, createHealthResponseSchema, createSuccessResponseSchema, createWrappedHealthResponseSchema } from './openapi-schemas.js';
|
|
14
|
+
export { CommonTags, ErrorResponseFormat, badRequestWrapped, createErrorHandlers, createOpenAPIDocConfig, createValidationHook, createdWrapped, notFoundWrapped, okWrapped, serverErrorWrapped, simpleValidationHook, wrappedValidationHook } from './openapi-config.js';
|
|
15
|
+
import '@wolffm/logger/worker';
|
|
16
|
+
import '@hono/zod-openapi';
|
|
17
|
+
import 'hono/utils/types';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Edge-auth verification — the backend half of the centralized auth channel.
|
|
21
|
+
*
|
|
22
|
+
* edge-router (hadoku.me) resolves the caller's tier once, then stamps every
|
|
23
|
+
* proxied request with:
|
|
24
|
+
* - `X-Edge-Auth: <EDGE_AUTH_SECRET>` — provenance (proves it came via edge)
|
|
25
|
+
* - `X-Hadoku-Tier: public|friend|service|wife|admin` — edge-resolved tier
|
|
26
|
+
* - `X-User-Key: <key>` — the resolved key (for audit trails)
|
|
27
|
+
*
|
|
28
|
+
* A backend worker calls `verifyEdgeAuth` / `createEdgeAuth` instead of
|
|
29
|
+
* resolving the key itself — only edge-router touches the key registry.
|
|
30
|
+
*
|
|
31
|
+
* DEGRADE-TO-PUBLIC, not refuse. Workers are reachable directly at
|
|
32
|
+
* `*.workers.dev` bypassing edge — and some of those direct hits are
|
|
33
|
+
* legitimate and PUBLIC (the monitoring probe hits `/…/health` directly with
|
|
34
|
+
* no auth header). So a request without valid `X-Edge-Auth` is treated as
|
|
35
|
+
* `public`, NOT refused: the edge-stamped `X-Hadoku-Tier` is only trusted when
|
|
36
|
+
* provenance checks out. This blocks tier-forgery (you can't send
|
|
37
|
+
* `X-Hadoku-Tier: admin` directly and be believed) while keeping public routes
|
|
38
|
+
* reachable. Protected routes still gate via `requireMinTier`, which 403s a
|
|
39
|
+
* public caller — so the provenance check effectively gates tier ELEVATION,
|
|
40
|
+
* not request admission.
|
|
41
|
+
*
|
|
42
|
+
* IDENTITY IS THE KEY'S, AND ONLY THE EDGE SAYS WHOSE KEY IT IS. edge-router
|
|
43
|
+
* resolves the caller's key against the registry and stamps `X-User-Id` /
|
|
44
|
+
* `X-User-Name` from that record (stripping any client copy first). On a direct
|
|
45
|
+
* hit nothing did that, so those headers are whatever the caller typed — and
|
|
46
|
+
* until 2026-10-09 they reached the app anyway: this middleware downgraded the
|
|
47
|
+
* TIER but left the identity headers on the request, and task and study read
|
|
48
|
+
* `X-User-Id` straight off it. Anyone could name any userId and act as that
|
|
49
|
+
* person. Now an unverified request has every edge-stamped identity header
|
|
50
|
+
* REMOVED before any handler runs (`c.req.raw` is replaced with a scrubbed
|
|
51
|
+
* copy), so code that reads the header directly — the common shape — sees no
|
|
52
|
+
* identity at all rather than a forged one. Verified requests are untouched,
|
|
53
|
+
* and their identity is also on `authContext.userId` / `.userName`, which is
|
|
54
|
+
* where new code should read it.
|
|
55
|
+
*
|
|
56
|
+
* `createEdgeAuth` is a drop-in for `createHadokuAuth`: it sets the same
|
|
57
|
+
* `authContext` ({ userType, credential }) and supports the same `extend`
|
|
58
|
+
* option, so a worker migrates by swapping the factory name. Keep the route's
|
|
59
|
+
* `requireMinTier(...)` gate — it's the backstop that turns "public direct
|
|
60
|
+
* hit" into a 403 on protected routes.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/** Environment shape required by edge-auth — just the shared secret. */
|
|
64
|
+
interface EdgeAuthEnv {
|
|
65
|
+
EDGE_AUTH_SECRET?: string;
|
|
66
|
+
}
|
|
67
|
+
type EdgeAuthResult = {
|
|
68
|
+
ok: true;
|
|
69
|
+
userType: HadokuAuthContext['userType'];
|
|
70
|
+
credential: string | null;
|
|
71
|
+
userId: string | null;
|
|
72
|
+
userName: string | null;
|
|
73
|
+
} | {
|
|
74
|
+
ok: false;
|
|
75
|
+
reason: 'misconfigured' | 'bad-provenance';
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Verify a request arrived via edge-router and extract the edge-resolved tier.
|
|
79
|
+
*
|
|
80
|
+
* - `misconfigured`: the worker has no EDGE_AUTH_SECRET bound — fail CLOSED.
|
|
81
|
+
* (Every worker got the secret in Step 1; a missing one is a deploy fault,
|
|
82
|
+
* and degrading to public would be a security hole.)
|
|
83
|
+
* - `bad-provenance`: X-Edge-Auth absent or mismatched — a direct
|
|
84
|
+
* *.workers.dev hit, not edge traffic.
|
|
85
|
+
*
|
|
86
|
+
* On success, `userType` comes from X-Hadoku-Tier (defaults to 'public' if the
|
|
87
|
+
* header is absent/garbage but provenance is valid — e.g. an edge route that
|
|
88
|
+
* stamps auth but resolved an anonymous caller).
|
|
89
|
+
*/
|
|
90
|
+
declare function verifyEdgeAuth(c: Context, env: EdgeAuthEnv): EdgeAuthResult;
|
|
91
|
+
/**
|
|
92
|
+
* Drop-in replacement for `createHadokuAuth`. Sets `authContext` to
|
|
93
|
+
* `{ userType, credential }` (+ any `extend` fields). When provenance is
|
|
94
|
+
* valid, the tier comes from the edge-stamped header; otherwise it degrades to
|
|
95
|
+
* `public` (see the module header — never refuses, never trusts an unverified
|
|
96
|
+
* tier). Never short-circuits: routes gate via `requireMinTier`.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* // Before: app.use('*', createHadokuAuth<Env, Extra>({ extend }));
|
|
101
|
+
* // After: app.use('*', createEdgeAuth<Env, Extra>({ extend }));
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
declare function createEdgeAuth<TExtra extends Record<string, unknown> = Record<string, never>>(options?: HadokuAuthOptions<TExtra> & {
|
|
105
|
+
contextKey?: string;
|
|
106
|
+
}): MiddlewareHandler;
|
|
107
|
+
|
|
108
|
+
export { type EdgeAuthEnv, type EdgeAuthResult, HadokuAuthContext, HadokuAuthOptions, createEdgeAuth, verifyEdgeAuth };
|