@sudobility/shapeshyft_service 1.0.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/CLAUDE.md +35 -0
- package/README.md +17 -0
- package/dist/context.d.ts +62 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +55 -0
- package/dist/context.js.map +1 -0
- package/dist/contracts.d.ts +110 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +7 -0
- package/dist/contracts.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-key.d.ts +22 -0
- package/dist/lib/api-key.d.ts.map +1 -0
- package/dist/lib/api-key.js +87 -0
- package/dist/lib/api-key.js.map +1 -0
- package/dist/lib/encryption.d.ts +22 -0
- package/dist/lib/encryption.d.ts.map +1 -0
- package/dist/lib/encryption.js +41 -0
- package/dist/lib/encryption.js.map +1 -0
- package/dist/lib/entity-api-key.d.ts +21 -0
- package/dist/lib/entity-api-key.d.ts.map +1 -0
- package/dist/lib/entity-api-key.js +54 -0
- package/dist/lib/entity-api-key.js.map +1 -0
- package/dist/lib/entity-helpers.d.ts +90 -0
- package/dist/lib/entity-helpers.d.ts.map +1 -0
- package/dist/lib/entity-helpers.js +136 -0
- package/dist/lib/entity-helpers.js.map +1 -0
- package/dist/lib/money.d.ts +7 -0
- package/dist/lib/money.d.ts.map +1 -0
- package/dist/lib/money.js +9 -0
- package/dist/lib/money.js.map +1 -0
- package/dist/lib/public-project.d.ts +14 -0
- package/dist/lib/public-project.d.ts.map +1 -0
- package/dist/lib/public-project.js +17 -0
- package/dist/lib/public-project.js.map +1 -0
- package/dist/lib/user-api-key-cache.d.ts +35 -0
- package/dist/lib/user-api-key-cache.d.ts.map +1 -0
- package/dist/lib/user-api-key-cache.js +99 -0
- package/dist/lib/user-api-key-cache.js.map +1 -0
- package/dist/lib/user-api-key.d.ts +46 -0
- package/dist/lib/user-api-key.d.ts.map +1 -0
- package/dist/lib/user-api-key.js +102 -0
- package/dist/lib/user-api-key.js.map +1 -0
- package/dist/middleware/firebaseAuth.d.ts +34 -0
- package/dist/middleware/firebaseAuth.d.ts.map +1 -0
- package/dist/middleware/firebaseAuth.js +133 -0
- package/dist/middleware/firebaseAuth.js.map +1 -0
- package/dist/middleware/rateLimit.d.ts +29 -0
- package/dist/middleware/rateLimit.d.ts.map +1 -0
- package/dist/middleware/rateLimit.js +85 -0
- package/dist/middleware/rateLimit.js.map +1 -0
- package/dist/middleware/subscription.d.ts +8 -0
- package/dist/middleware/subscription.d.ts.map +1 -0
- package/dist/middleware/subscription.js +19 -0
- package/dist/middleware/subscription.js.map +1 -0
- package/dist/routes/ai.d.ts +10 -0
- package/dist/routes/ai.d.ts.map +1 -0
- package/dist/routes/ai.js +698 -0
- package/dist/routes/ai.js.map +1 -0
- package/dist/routes/analytics.d.ts +9 -0
- package/dist/routes/analytics.d.ts.map +1 -0
- package/dist/routes/analytics.js +192 -0
- package/dist/routes/analytics.js.map +1 -0
- package/dist/routes/endpoints.d.ts +10 -0
- package/dist/routes/endpoints.d.ts.map +1 -0
- package/dist/routes/endpoints.js +255 -0
- package/dist/routes/endpoints.js.map +1 -0
- package/dist/routes/entities.d.ts +15 -0
- package/dist/routes/entities.d.ts.map +1 -0
- package/dist/routes/entities.js +332 -0
- package/dist/routes/entities.js.map +1 -0
- package/dist/routes/entity-api-keys.d.ts +17 -0
- package/dist/routes/entity-api-keys.d.ts.map +1 -0
- package/dist/routes/entity-api-keys.js +142 -0
- package/dist/routes/entity-api-keys.js.map +1 -0
- package/dist/routes/invitations.d.ts +15 -0
- package/dist/routes/invitations.d.ts.map +1 -0
- package/dist/routes/invitations.js +58 -0
- package/dist/routes/invitations.js.map +1 -0
- package/dist/routes/projects.d.ts +9 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +241 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/providers.d.ts +9 -0
- package/dist/routes/providers.d.ts.map +1 -0
- package/dist/routes/providers.js +89 -0
- package/dist/routes/providers.js.map +1 -0
- package/dist/routes/ratelimits.d.ts +9 -0
- package/dist/routes/ratelimits.d.ts.map +1 -0
- package/dist/routes/ratelimits.js +171 -0
- package/dist/routes/ratelimits.js.map +1 -0
- package/dist/routes/settings.d.ts +9 -0
- package/dist/routes/settings.d.ts.map +1 -0
- package/dist/routes/settings.js +138 -0
- package/dist/routes/settings.js.map +1 -0
- package/dist/routes/storage.d.ts +9 -0
- package/dist/routes/storage.d.ts.map +1 -0
- package/dist/routes/storage.js +180 -0
- package/dist/routes/storage.js.map +1 -0
- package/dist/routes/user-api-keys.d.ts +14 -0
- package/dist/routes/user-api-keys.d.ts.map +1 -0
- package/dist/routes/user-api-keys.js +237 -0
- package/dist/routes/user-api-keys.js.map +1 -0
- package/dist/routes/users.d.ts +7 -0
- package/dist/routes/users.d.ts.map +1 -0
- package/dist/routes/users.js +102 -0
- package/dist/routes/users.js.map +1 -0
- package/dist/schema/init.d.ts +12 -0
- package/dist/schema/init.d.ts.map +1 -0
- package/dist/schema/init.js +404 -0
- package/dist/schema/init.js.map +1 -0
- package/dist/schema/tables.d.ts +2110 -0
- package/dist/schema/tables.d.ts.map +1 -0
- package/dist/schema/tables.js +251 -0
- package/dist/schema/tables.js.map +1 -0
- package/dist/schemas/index.d.ts +671 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +299 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/service.d.ts +41 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +70 -0
- package/dist/service.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Firebase authentication middleware
|
|
3
|
+
*
|
|
4
|
+
* Accepts a personal API key, an entity API key, or a Firebase ID token. Key
|
|
5
|
+
* prefixes, auth functions, and storage all come from the service context.
|
|
6
|
+
*/
|
|
7
|
+
import { errorResponse } from "@sudobility/shapeshyft_engine/types";
|
|
8
|
+
import { eq } from "drizzle-orm";
|
|
9
|
+
/**
|
|
10
|
+
* Path prefix an entity API key may reach.
|
|
11
|
+
*
|
|
12
|
+
* Entity keys are scoped to entity-owned resources: projects, endpoints,
|
|
13
|
+
* provider keys, storage, analytics. They must not reach `/users/...`, which
|
|
14
|
+
* would let a key read or mint credentials belonging to the person who created
|
|
15
|
+
* it -- an escalation from "acts as the entity" to "acts as its author".
|
|
16
|
+
*/
|
|
17
|
+
const ENTITY_API_KEY_PATH_PREFIX = "/entities/";
|
|
18
|
+
export function createFirebaseAuthMiddleware(ctx) {
|
|
19
|
+
const { db, logger } = ctx;
|
|
20
|
+
const { users } = ctx.tables;
|
|
21
|
+
const { extractUserApiKeyFromHeaders } = ctx.userApiKeys;
|
|
22
|
+
const { resolveUserApiKey } = ctx.userApiKeyCache;
|
|
23
|
+
const { extractEntityApiKeyFromHeaders } = ctx.entityApiKeyFormat;
|
|
24
|
+
const { entityHelpers } = ctx.entityAccess;
|
|
25
|
+
const { verifyIdToken, isSiteAdmin, isAnonymousUser } = ctx.auth;
|
|
26
|
+
const userPrefix = ctx.userApiKeys.prefix;
|
|
27
|
+
const entityPrefix = ctx.entityApiKeyFormat.prefixWithSeparator;
|
|
28
|
+
/**
|
|
29
|
+
* Ensure user record exists in database.
|
|
30
|
+
* Uses firebase_uid as the primary key.
|
|
31
|
+
*/
|
|
32
|
+
async function ensureUserExists(firebaseUid, email) {
|
|
33
|
+
const existing = await db
|
|
34
|
+
.select()
|
|
35
|
+
.from(users)
|
|
36
|
+
.where(eq(users.firebase_uid, firebaseUid));
|
|
37
|
+
if (existing.length === 0) {
|
|
38
|
+
await db.insert(users).values({
|
|
39
|
+
firebase_uid: firebaseUid,
|
|
40
|
+
email: email ?? null,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Firebase authentication middleware.
|
|
46
|
+
*
|
|
47
|
+
* Verifies Firebase token and sets context variables:
|
|
48
|
+
* - firebaseUser: The decoded Firebase token
|
|
49
|
+
* - userId: The Firebase UID
|
|
50
|
+
* - userEmail: The user's email (or null)
|
|
51
|
+
* - siteAdmin: Whether the user is a site admin
|
|
52
|
+
*
|
|
53
|
+
* Also ensures user record exists in database (fire-and-forget).
|
|
54
|
+
*/
|
|
55
|
+
async function firebaseAuthMiddleware(c, next) {
|
|
56
|
+
// 1. Personal API key ("<user prefix>..."), from X-API-Key or Authorization: Bearer.
|
|
57
|
+
// A resolved key sets exactly the same context variables a token would, minus
|
|
58
|
+
// firebaseUser, so every downstream handler behaves identically.
|
|
59
|
+
const userApiKey = extractUserApiKeyFromHeaders(name => c.req.header(name));
|
|
60
|
+
if (userApiKey) {
|
|
61
|
+
const resolved = await resolveUserApiKey(userApiKey);
|
|
62
|
+
if (!resolved) {
|
|
63
|
+
return c.json(errorResponse("Invalid or inactive API key"), 401);
|
|
64
|
+
}
|
|
65
|
+
c.set("userId", resolved.userId);
|
|
66
|
+
c.set("userEmail", resolved.userEmail);
|
|
67
|
+
c.set("siteAdmin", isSiteAdmin(resolved.userEmail));
|
|
68
|
+
c.set("authMethod", "api_key");
|
|
69
|
+
c.set("apiKeyId", resolved.keyId);
|
|
70
|
+
await next();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// 2. Entity API key ("<entity prefix>_..."). Authenticates as the entity itself: no
|
|
74
|
+
// user identity, so downstream permission checks match on the entity instead
|
|
75
|
+
// of a membership role. `userId` is still populated with the key's author so
|
|
76
|
+
// audit columns keep a value.
|
|
77
|
+
const entityApiKey = extractEntityApiKeyFromHeaders(name => c.req.header(name));
|
|
78
|
+
if (entityApiKey) {
|
|
79
|
+
if (!entityHelpers.apiKeys) {
|
|
80
|
+
return c.json(errorResponse("Entity API keys are not enabled"), 501);
|
|
81
|
+
}
|
|
82
|
+
const identity = await entityHelpers.apiKeys.verifyKey(entityApiKey);
|
|
83
|
+
if (!identity) {
|
|
84
|
+
return c.json(errorResponse("Invalid or inactive API key"), 401);
|
|
85
|
+
}
|
|
86
|
+
if (!c.req.path.includes(ENTITY_API_KEY_PATH_PREFIX)) {
|
|
87
|
+
return c.json(errorResponse("Entity API keys may only access entity-scoped routes. Use a Firebase token or personal API key for this request."), 403);
|
|
88
|
+
}
|
|
89
|
+
c.set("userId", identity.createdByUserId);
|
|
90
|
+
c.set("userEmail", null);
|
|
91
|
+
c.set("siteAdmin", false);
|
|
92
|
+
c.set("authMethod", "entity_api_key");
|
|
93
|
+
c.set("entityApiKeyId", identity.keyId);
|
|
94
|
+
c.set("entityApiKeyEntityId", identity.entityId);
|
|
95
|
+
// Best-effort usage bookkeeping; never blocks the request
|
|
96
|
+
entityHelpers.apiKeys
|
|
97
|
+
.touchLastUsed(identity.keyId)
|
|
98
|
+
.catch(err => logger.error("Failed to stamp API key usage:", err));
|
|
99
|
+
await next();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// 3. Firebase ID token
|
|
103
|
+
const authHeader = c.req.header("Authorization");
|
|
104
|
+
if (!authHeader) {
|
|
105
|
+
return c.json(errorResponse(`Authorization required. Provide a Firebase ID token as 'Authorization: Bearer <token>', a personal API key as 'X-API-Key: ${userPrefix}...', or an entity API key as 'X-API-Key: ${entityPrefix}...'`), 401);
|
|
106
|
+
}
|
|
107
|
+
const [type, token] = authHeader.split(" ");
|
|
108
|
+
if (type !== "Bearer" || !token) {
|
|
109
|
+
return c.json(errorResponse("Invalid authorization format. Use: Bearer <token>"), 401);
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const decodedToken = await verifyIdToken(token);
|
|
113
|
+
if (isAnonymousUser(decodedToken)) {
|
|
114
|
+
return c.json(errorResponse("Anonymous users cannot access this resource"), 403);
|
|
115
|
+
}
|
|
116
|
+
const userId = decodedToken.uid;
|
|
117
|
+
const userEmail = decodedToken.email ?? null;
|
|
118
|
+
c.set("firebaseUser", decodedToken);
|
|
119
|
+
c.set("userId", userId);
|
|
120
|
+
c.set("userEmail", userEmail);
|
|
121
|
+
c.set("siteAdmin", isSiteAdmin(userEmail));
|
|
122
|
+
c.set("authMethod", "firebase");
|
|
123
|
+
// Ensure user exists in database (fire-and-forget)
|
|
124
|
+
ensureUserExists(userId, userEmail).catch(err => logger.error("Failed to ensure user exists:", err));
|
|
125
|
+
await next();
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return c.json(errorResponse("Invalid or expired Firebase token"), 401);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return firebaseAuthMiddleware;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=firebaseAuth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firebaseAuth.js","sourceRoot":"","sources":["../../src/middleware/firebaseAuth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Context, Next } from "hono";
|
|
2
|
+
import { RateLimitRouteHandler, type RateLimitsConfig } from "@sudobility/ratelimit_service";
|
|
3
|
+
import type { ServiceDb } from "../contracts.js";
|
|
4
|
+
import type { ServiceTables } from "../schema/tables.js";
|
|
5
|
+
/**
|
|
6
|
+
* Default rate limit tiers
|
|
7
|
+
*
|
|
8
|
+
* - none: Free tier users (no subscription)
|
|
9
|
+
* - bandwidth_dev: Users with dev bandwidth entitlement
|
|
10
|
+
* - bandwidth_pro: Pro users with higher limits
|
|
11
|
+
* - bandwidth_ultra: Enterprise users with unlimited access
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_RATE_LIMITS_CONFIG: RateLimitsConfig;
|
|
14
|
+
export declare const DEFAULT_ENTITLEMENT_DISPLAY_NAMES: Record<string, string>;
|
|
15
|
+
export declare function createRateLimiting(opts: {
|
|
16
|
+
db: ServiceDb;
|
|
17
|
+
rateLimitCounters: ServiceTables["rateLimitCounters"];
|
|
18
|
+
revenueCatApiKey: string | undefined;
|
|
19
|
+
rateLimitsConfig: RateLimitsConfig;
|
|
20
|
+
entitlementDisplayNames: Record<string, string>;
|
|
21
|
+
}): {
|
|
22
|
+
rateLimitsConfig: RateLimitsConfig;
|
|
23
|
+
entitlementDisplayNames: Record<string, string>;
|
|
24
|
+
getRateLimitRouteHandler: () => RateLimitRouteHandler;
|
|
25
|
+
rateLimitHandler: (c: Context, next: Next) => Promise<void>;
|
|
26
|
+
getTestMode: (c: Context) => boolean;
|
|
27
|
+
};
|
|
28
|
+
export type RateLimiting = ReturnType<typeof createRateLimiting>;
|
|
29
|
+
//# sourceMappingURL=rateLimit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rateLimit.d.ts","sourceRoot":"","sources":["../../src/middleware/rateLimit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAEL,qBAAqB,EACrB,KAAK,gBAAgB,EACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,EAAE,gBAKxC,CAAC;AAEF,eAAO,MAAM,iCAAiC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKpE,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,EAAE,EAAE,SAAS,CAAC;IACd,iBAAiB,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAC;IACtD,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjD;;;oCAuBsC,qBAAqB;0BAqCvB,OAAO,QAAQ,IAAI;qBA3C9B,OAAO,KAAG,OAAO;EAsD1C;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createRateLimitMiddleware, RateLimitRouteHandler, } from "@sudobility/ratelimit_service";
|
|
2
|
+
/**
|
|
3
|
+
* Default rate limit tiers
|
|
4
|
+
*
|
|
5
|
+
* - none: Free tier users (no subscription)
|
|
6
|
+
* - bandwidth_dev: Users with dev bandwidth entitlement
|
|
7
|
+
* - bandwidth_pro: Pro users with higher limits
|
|
8
|
+
* - bandwidth_ultra: Enterprise users with unlimited access
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_RATE_LIMITS_CONFIG = {
|
|
11
|
+
none: { hourly: 10, daily: 120, monthly: 1800 },
|
|
12
|
+
bandwidth_dev: { hourly: 100, daily: 1200, monthly: 18000 },
|
|
13
|
+
bandwidth_pro: { hourly: 800, daily: 10000, monthly: 150000 },
|
|
14
|
+
bandwidth_ultra: { hourly: undefined, daily: undefined, monthly: undefined },
|
|
15
|
+
};
|
|
16
|
+
export const DEFAULT_ENTITLEMENT_DISPLAY_NAMES = {
|
|
17
|
+
none: "Free",
|
|
18
|
+
bandwidth_dev: "Developer",
|
|
19
|
+
bandwidth_pro: "Pro",
|
|
20
|
+
bandwidth_ultra: "Ultra",
|
|
21
|
+
};
|
|
22
|
+
export function createRateLimiting(opts) {
|
|
23
|
+
const { rateLimitsConfig, entitlementDisplayNames } = opts;
|
|
24
|
+
// Lazily initialized so a missing RevenueCat key fails on first use, not at boot
|
|
25
|
+
let routeHandler = null;
|
|
26
|
+
let middleware = null;
|
|
27
|
+
function requireRevenueCatKey() {
|
|
28
|
+
if (!opts.revenueCatApiKey) {
|
|
29
|
+
throw new Error("Required environment variable REVENUECAT_API_KEY is not set");
|
|
30
|
+
}
|
|
31
|
+
return opts.revenueCatApiKey;
|
|
32
|
+
}
|
|
33
|
+
/** Extract testMode from URL query parameter to filter sandbox purchases. */
|
|
34
|
+
function getTestMode(c) {
|
|
35
|
+
const url = new URL(c.req.url);
|
|
36
|
+
return url.searchParams.get("testMode") === "true";
|
|
37
|
+
}
|
|
38
|
+
/** The route handler for rate limit endpoints. */
|
|
39
|
+
function getRateLimitRouteHandler() {
|
|
40
|
+
if (!routeHandler) {
|
|
41
|
+
routeHandler = new RateLimitRouteHandler({
|
|
42
|
+
revenueCatApiKey: requireRevenueCatKey(),
|
|
43
|
+
rateLimitsConfig,
|
|
44
|
+
db: opts.db,
|
|
45
|
+
rateLimitsTable: opts.rateLimitCounters,
|
|
46
|
+
entitlementDisplayNames,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return routeHandler;
|
|
50
|
+
}
|
|
51
|
+
function getMiddleware() {
|
|
52
|
+
if (!middleware) {
|
|
53
|
+
middleware = createRateLimitMiddleware({
|
|
54
|
+
revenueCatApiKey: requireRevenueCatKey(),
|
|
55
|
+
rateLimitsConfig,
|
|
56
|
+
// Cast to any to avoid type conflicts between different drizzle-orm/hono
|
|
57
|
+
// instances when using bun link for local development
|
|
58
|
+
db: opts.db,
|
|
59
|
+
rateLimitsTable: opts.rateLimitCounters,
|
|
60
|
+
getUserId: (c) => {
|
|
61
|
+
// Set by the auth middleware for every auth method
|
|
62
|
+
const userId = c.get("userId");
|
|
63
|
+
if (!userId) {
|
|
64
|
+
throw new Error("Authenticated user not found in context");
|
|
65
|
+
}
|
|
66
|
+
return userId;
|
|
67
|
+
},
|
|
68
|
+
getTestMode: (c) => getTestMode(c),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return middleware;
|
|
72
|
+
}
|
|
73
|
+
/** Rate limit middleware for endpoints that need it. */
|
|
74
|
+
async function rateLimitHandler(c, next) {
|
|
75
|
+
await getMiddleware()(c, next);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
rateLimitsConfig,
|
|
79
|
+
entitlementDisplayNames,
|
|
80
|
+
getRateLimitRouteHandler,
|
|
81
|
+
rateLimitHandler,
|
|
82
|
+
getTestMode,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=rateLimit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rateLimit.js","sourceRoot":"","sources":["../../src/middleware/rateLimit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import { SubscriptionHelper } from "@sudobility/subscription_service";
|
|
3
|
+
export declare function createSubscriptionAccess(revenueCatApiKey: string | undefined): {
|
|
4
|
+
getSubscriptionHelper: () => SubscriptionHelper | null;
|
|
5
|
+
getTestMode: (c: Context) => boolean;
|
|
6
|
+
};
|
|
7
|
+
export type SubscriptionAccess = ReturnType<typeof createSubscriptionAccess>;
|
|
8
|
+
//# sourceMappingURL=subscription.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../src/middleware/subscription.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAEtE,wBAAgB,wBAAwB,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS;iCAIzC,kBAAkB,GAAG,IAAI;qBAQnC,OAAO,KAAG,OAAO;EAM1C;AAED,MAAM,MAAM,kBAAkB,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SubscriptionHelper } from "@sudobility/subscription_service";
|
|
2
|
+
export function createSubscriptionAccess(revenueCatApiKey) {
|
|
3
|
+
let subscriptionHelper = null;
|
|
4
|
+
/** Null when RevenueCat is not configured. */
|
|
5
|
+
function getSubscriptionHelper() {
|
|
6
|
+
if (!revenueCatApiKey)
|
|
7
|
+
return null;
|
|
8
|
+
if (!subscriptionHelper) {
|
|
9
|
+
subscriptionHelper = new SubscriptionHelper({ revenueCatApiKey });
|
|
10
|
+
}
|
|
11
|
+
return subscriptionHelper;
|
|
12
|
+
}
|
|
13
|
+
function getTestMode(c) {
|
|
14
|
+
const url = new URL(c.req.url);
|
|
15
|
+
return url.searchParams.get("testMode") === "true";
|
|
16
|
+
}
|
|
17
|
+
return { getSubscriptionHelper, getTestMode };
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=subscription.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription.js","sourceRoot":"","sources":["../../src/middleware/subscription.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAEtE,MAAM,UAAU,wBAAwB,CAAC,gBAAoC;IAC3E,IAAI,kBAAkB,GAA8B,IAAI,CAAC;IAEzD,8CAA8C;IAC9C,SAAS,qBAAqB;QAC5B,IAAI,CAAC,gBAAgB;YAAE,OAAO,IAAI,CAAC;QACnC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACxB,kBAAkB,GAAG,IAAI,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IAED,SAAS,WAAW,CAAC,CAAU;QAC7B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,MAAM,CAAC;IACrD,CAAC;IAED,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Public AI inference routes
|
|
3
|
+
* @description Handles AI endpoint invocation and prompt generation.
|
|
4
|
+
* These routes use project API key authentication (not Firebase),
|
|
5
|
+
* with optional IP allowlisting and rate limiting per entity.
|
|
6
|
+
*/
|
|
7
|
+
import { Hono } from "hono";
|
|
8
|
+
import type { ServiceContext } from "../context.js";
|
|
9
|
+
export declare function createAiRouter(ctx: ServiceContext): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
10
|
+
//# sourceMappingURL=ai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/routes/ai.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AA6B5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AASpD,wBAAgB,cAAc,CAAC,GAAG,EAAE,cAAc,8EAm5BjD"}
|