@adakrpos/auth 0.0.1

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/dist/index.js ADDED
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ clearApiKeyCache: () => clearApiKeyCache,
24
+ createAdakrposAuth: () => createAdakrposAuth,
25
+ getCachedApiKeyValidity: () => getCachedApiKeyValidity,
26
+ setCachedApiKeyValidity: () => setCachedApiKeyValidity
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/cache.ts
31
+ var cache = /* @__PURE__ */ new Map();
32
+ var DEFAULT_CACHE_TTL_MS = 3e4;
33
+ function getCachedApiKeyValidity(apiKey) {
34
+ const entry = cache.get(apiKey);
35
+ if (!entry) {
36
+ return null;
37
+ }
38
+ if (Date.now() > entry.expiresAt) {
39
+ cache.delete(apiKey);
40
+ return null;
41
+ }
42
+ return entry.valid;
43
+ }
44
+ function setCachedApiKeyValidity(apiKey, valid, ttlMs = DEFAULT_CACHE_TTL_MS) {
45
+ cache.set(apiKey, {
46
+ valid,
47
+ expiresAt: Date.now() + ttlMs
48
+ });
49
+ }
50
+ function clearApiKeyCache() {
51
+ cache.clear();
52
+ }
53
+
54
+ // src/client.ts
55
+ var DEFAULT_AUTH_URL = "https://ada-kr-pos.com";
56
+ function createUrl(baseUrl, path) {
57
+ return new URL(path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
58
+ }
59
+ async function parseJson(response) {
60
+ return await response.json();
61
+ }
62
+ function createAdakrposAuth(config) {
63
+ const baseUrl = config.authUrl ?? DEFAULT_AUTH_URL;
64
+ const apiKey = config.apiKey;
65
+ async function request(path, init, options = {}) {
66
+ if (getCachedApiKeyValidity(apiKey) === false) {
67
+ return null;
68
+ }
69
+ const response = await fetch(createUrl(baseUrl, path), {
70
+ ...init,
71
+ headers: {
72
+ Authorization: `Bearer ${apiKey}`,
73
+ ...init.body ? { "Content-Type": "application/json" } : {},
74
+ ...init.headers
75
+ }
76
+ });
77
+ if (response.status === 401 || response.status === 403) {
78
+ setCachedApiKeyValidity(apiKey, false);
79
+ return null;
80
+ }
81
+ setCachedApiKeyValidity(apiKey, true);
82
+ if (response.status === 404 && options.returnNullOnNotFound) {
83
+ return null;
84
+ }
85
+ if (!response.ok) {
86
+ throw new Error(`Adakrpos auth request failed with status ${response.status}`);
87
+ }
88
+ return parseJson(response);
89
+ }
90
+ return {
91
+ async verifySession(sessionId) {
92
+ return request(
93
+ "/api/sdk/verify-session",
94
+ {
95
+ method: "POST",
96
+ body: JSON.stringify({ sessionId })
97
+ },
98
+ { returnNullOnNotFound: true }
99
+ );
100
+ },
101
+ async getUser(userId) {
102
+ return request(`/api/sdk/users/${encodeURIComponent(userId)}`, {
103
+ method: "GET"
104
+ }, { returnNullOnNotFound: true });
105
+ },
106
+ async getCurrentUser(sessionId) {
107
+ const result = await this.verifySession(sessionId);
108
+ return result?.user ?? null;
109
+ }
110
+ };
111
+ }
112
+ // Annotate the CommonJS export names for ESM import in node:
113
+ 0 && (module.exports = {
114
+ clearApiKeyCache,
115
+ createAdakrposAuth,
116
+ getCachedApiKeyValidity,
117
+ setCachedApiKeyValidity
118
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,88 @@
1
+ // src/cache.ts
2
+ var cache = /* @__PURE__ */ new Map();
3
+ var DEFAULT_CACHE_TTL_MS = 3e4;
4
+ function getCachedApiKeyValidity(apiKey) {
5
+ const entry = cache.get(apiKey);
6
+ if (!entry) {
7
+ return null;
8
+ }
9
+ if (Date.now() > entry.expiresAt) {
10
+ cache.delete(apiKey);
11
+ return null;
12
+ }
13
+ return entry.valid;
14
+ }
15
+ function setCachedApiKeyValidity(apiKey, valid, ttlMs = DEFAULT_CACHE_TTL_MS) {
16
+ cache.set(apiKey, {
17
+ valid,
18
+ expiresAt: Date.now() + ttlMs
19
+ });
20
+ }
21
+ function clearApiKeyCache() {
22
+ cache.clear();
23
+ }
24
+
25
+ // src/client.ts
26
+ var DEFAULT_AUTH_URL = "https://ada-kr-pos.com";
27
+ function createUrl(baseUrl, path) {
28
+ return new URL(path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
29
+ }
30
+ async function parseJson(response) {
31
+ return await response.json();
32
+ }
33
+ function createAdakrposAuth(config) {
34
+ const baseUrl = config.authUrl ?? DEFAULT_AUTH_URL;
35
+ const apiKey = config.apiKey;
36
+ async function request(path, init, options = {}) {
37
+ if (getCachedApiKeyValidity(apiKey) === false) {
38
+ return null;
39
+ }
40
+ const response = await fetch(createUrl(baseUrl, path), {
41
+ ...init,
42
+ headers: {
43
+ Authorization: `Bearer ${apiKey}`,
44
+ ...init.body ? { "Content-Type": "application/json" } : {},
45
+ ...init.headers
46
+ }
47
+ });
48
+ if (response.status === 401 || response.status === 403) {
49
+ setCachedApiKeyValidity(apiKey, false);
50
+ return null;
51
+ }
52
+ setCachedApiKeyValidity(apiKey, true);
53
+ if (response.status === 404 && options.returnNullOnNotFound) {
54
+ return null;
55
+ }
56
+ if (!response.ok) {
57
+ throw new Error(`Adakrpos auth request failed with status ${response.status}`);
58
+ }
59
+ return parseJson(response);
60
+ }
61
+ return {
62
+ async verifySession(sessionId) {
63
+ return request(
64
+ "/api/sdk/verify-session",
65
+ {
66
+ method: "POST",
67
+ body: JSON.stringify({ sessionId })
68
+ },
69
+ { returnNullOnNotFound: true }
70
+ );
71
+ },
72
+ async getUser(userId) {
73
+ return request(`/api/sdk/users/${encodeURIComponent(userId)}`, {
74
+ method: "GET"
75
+ }, { returnNullOnNotFound: true });
76
+ },
77
+ async getCurrentUser(sessionId) {
78
+ const result = await this.verifySession(sessionId);
79
+ return result?.user ?? null;
80
+ }
81
+ };
82
+ }
83
+ export {
84
+ clearApiKeyCache,
85
+ createAdakrposAuth,
86
+ getCachedApiKeyValidity,
87
+ setCachedApiKeyValidity
88
+ };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@adakrpos/auth",
3
+ "version": "0.0.1",
4
+ "type": "commonjs",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js",
13
+ "default": "./dist/index.mjs"
14
+ },
15
+ "./hono": {
16
+ "types": "./dist/hono.d.ts",
17
+ "import": "./dist/hono.mjs",
18
+ "require": "./dist/hono.js",
19
+ "default": "./dist/hono.mjs"
20
+ },
21
+ "./express": {
22
+ "types": "./dist/express.d.ts",
23
+ "import": "./dist/express.mjs",
24
+ "require": "./dist/express.js",
25
+ "default": "./dist/express.mjs"
26
+ },
27
+ "./generic": {
28
+ "types": "./dist/generic.d.ts",
29
+ "import": "./dist/generic.mjs",
30
+ "require": "./dist/generic.js",
31
+ "default": "./dist/generic.mjs"
32
+ }
33
+ },
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "typecheck": "tsc --noEmit",
37
+ "test": "vitest run"
38
+ },
39
+ "devDependencies": {
40
+ "hono": "^4.12.7",
41
+ "tsup": "^8.4.0",
42
+ "typescript": "^5.7.3",
43
+ "vitest": "^3.0.5"
44
+ },
45
+ "peerDependencies": {
46
+ "hono": ">=4.0.0"
47
+ }
48
+ }
package/src/cache.ts ADDED
@@ -0,0 +1,38 @@
1
+ interface CacheEntry {
2
+ valid: boolean;
3
+ expiresAt: number;
4
+ }
5
+
6
+ const cache = new Map<string, CacheEntry>();
7
+
8
+ export const DEFAULT_CACHE_TTL_MS = 30_000;
9
+
10
+ export function getCachedApiKeyValidity(apiKey: string): boolean | null {
11
+ const entry = cache.get(apiKey);
12
+
13
+ if (!entry) {
14
+ return null;
15
+ }
16
+
17
+ if (Date.now() > entry.expiresAt) {
18
+ cache.delete(apiKey);
19
+ return null;
20
+ }
21
+
22
+ return entry.valid;
23
+ }
24
+
25
+ export function setCachedApiKeyValidity(
26
+ apiKey: string,
27
+ valid: boolean,
28
+ ttlMs: number = DEFAULT_CACHE_TTL_MS,
29
+ ): void {
30
+ cache.set(apiKey, {
31
+ valid,
32
+ expiresAt: Date.now() + ttlMs,
33
+ });
34
+ }
35
+
36
+ export function clearApiKeyCache(): void {
37
+ cache.clear();
38
+ }
package/src/client.ts ADDED
@@ -0,0 +1,93 @@
1
+ import {
2
+ getCachedApiKeyValidity,
3
+ setCachedApiKeyValidity,
4
+ } from "./cache";
5
+ import type { AdakrposSession, AdakrposUser } from "./types";
6
+
7
+ const DEFAULT_AUTH_URL = "https://ada-kr-pos.com";
8
+
9
+ export interface AdakrposAuthConfig {
10
+ apiKey: string;
11
+ authUrl?: string;
12
+ }
13
+
14
+ export interface AdakrposAuthClient {
15
+ verifySession(
16
+ sessionId: string,
17
+ ): Promise<{ user: AdakrposUser; session: AdakrposSession } | null>;
18
+ getUser(userId: string): Promise<AdakrposUser | null>;
19
+ getCurrentUser(sessionId: string): Promise<AdakrposUser | null>;
20
+ }
21
+
22
+ function createUrl(baseUrl: string, path: string): string {
23
+ return new URL(path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
24
+ }
25
+
26
+ async function parseJson<T>(response: Response): Promise<T> {
27
+ return (await response.json()) as T;
28
+ }
29
+
30
+ export function createAdakrposAuth(config: AdakrposAuthConfig): AdakrposAuthClient {
31
+ const baseUrl = config.authUrl ?? DEFAULT_AUTH_URL;
32
+ const apiKey = config.apiKey;
33
+
34
+ async function request<T>(
35
+ path: string,
36
+ init: RequestInit,
37
+ options: { returnNullOnNotFound?: boolean } = {},
38
+ ): Promise<T | null> {
39
+ if (getCachedApiKeyValidity(apiKey) === false) {
40
+ return null;
41
+ }
42
+
43
+ const response = await fetch(createUrl(baseUrl, path), {
44
+ ...init,
45
+ headers: {
46
+ Authorization: `Bearer ${apiKey}`,
47
+ ...(init.body ? { "Content-Type": "application/json" } : {}),
48
+ ...init.headers,
49
+ },
50
+ });
51
+
52
+ if (response.status === 401 || response.status === 403) {
53
+ setCachedApiKeyValidity(apiKey, false);
54
+ return null;
55
+ }
56
+
57
+ setCachedApiKeyValidity(apiKey, true);
58
+
59
+ if (response.status === 404 && options.returnNullOnNotFound) {
60
+ return null;
61
+ }
62
+
63
+ if (!response.ok) {
64
+ throw new Error(`Adakrpos auth request failed with status ${response.status}`);
65
+ }
66
+
67
+ return parseJson<T>(response);
68
+ }
69
+
70
+ return {
71
+ async verifySession(sessionId: string) {
72
+ return request<{ user: AdakrposUser; session: AdakrposSession }>(
73
+ "/api/sdk/verify-session",
74
+ {
75
+ method: "POST",
76
+ body: JSON.stringify({ sessionId }),
77
+ },
78
+ { returnNullOnNotFound: true },
79
+ );
80
+ },
81
+
82
+ async getUser(userId: string) {
83
+ return request<AdakrposUser>(`/api/sdk/users/${encodeURIComponent(userId)}`, {
84
+ method: "GET",
85
+ }, { returnNullOnNotFound: true });
86
+ },
87
+
88
+ async getCurrentUser(sessionId: string) {
89
+ const result = await this.verifySession(sessionId);
90
+ return result?.user ?? null;
91
+ },
92
+ };
93
+ }
package/src/express.ts ADDED
@@ -0,0 +1,94 @@
1
+ import { createAdakrposAuth } from "./client";
2
+ import type { AdakrposAuthConfig } from "./client";
3
+ import type { AuthContext, AdakrposAuthContext, AdakrposUnauthContext } from "./types";
4
+
5
+ // Augment Express Request type
6
+ declare global {
7
+ namespace Express {
8
+ interface Request {
9
+ auth?: () => Promise<AuthContext>;
10
+ }
11
+ }
12
+ }
13
+
14
+ const UNAUTH_CONTEXT: AdakrposUnauthContext = {
15
+ user: null,
16
+ session: null,
17
+ isAuthenticated: false,
18
+ };
19
+
20
+ function getSessionId(cookieHeader: string): string | null {
21
+ const sessionMatch = cookieHeader.match(/(?:^|;\s*)adakrpos_session=([^;]+)/);
22
+
23
+ if (!sessionMatch) {
24
+ return null;
25
+ }
26
+
27
+ try {
28
+ return decodeURIComponent(sessionMatch[1]);
29
+ } catch {
30
+ return sessionMatch[1];
31
+ }
32
+ }
33
+
34
+ function createAuthFn(client: any, sessionId: string | null): () => Promise<AuthContext> {
35
+ let authPromise: Promise<AuthContext> | undefined;
36
+
37
+ return async () => {
38
+ if (!authPromise) {
39
+ authPromise = (async () => {
40
+ if (!sessionId) {
41
+ return UNAUTH_CONTEXT;
42
+ }
43
+
44
+ const result = await client.verifySession(sessionId);
45
+
46
+ if (!result) {
47
+ return UNAUTH_CONTEXT;
48
+ }
49
+
50
+ return {
51
+ user: result.user,
52
+ session: result.session,
53
+ isAuthenticated: true,
54
+ } satisfies AdakrposAuthContext;
55
+ })();
56
+ }
57
+
58
+ return authPromise;
59
+ };
60
+ }
61
+
62
+ // Express middleware — attaches lazy auth function to req
63
+ export function adakrposAuthExpress(config: AdakrposAuthConfig) {
64
+ const client = createAdakrposAuth(config);
65
+
66
+ return async (req: any, res: any, next: any) => {
67
+ const cookieHeader = req.headers?.cookie ?? "";
68
+ const sessionId = getSessionId(cookieHeader);
69
+
70
+ // Lazy function — only calls server when invoked
71
+ req.auth = createAuthFn(client, sessionId);
72
+
73
+ next();
74
+ };
75
+ }
76
+
77
+ // Express middleware that requires authentication
78
+ export function requireAuthExpress(config: AdakrposAuthConfig) {
79
+ const client = createAdakrposAuth(config);
80
+
81
+ return async (req: any, res: any, next: any) => {
82
+ const cookieHeader = req.headers?.cookie ?? "";
83
+ const sessionId = getSessionId(cookieHeader);
84
+
85
+ req.auth = createAuthFn(client, sessionId);
86
+
87
+ const auth = await req.auth();
88
+ if (!auth?.isAuthenticated) {
89
+ return res.status(401).json({ error: "Unauthorized" });
90
+ }
91
+
92
+ next();
93
+ };
94
+ }
package/src/generic.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { createAdakrposAuth } from "./client";
2
+ import type { AdakrposAuthConfig } from "./client";
3
+ import type { AuthContext, AdakrposAuthContext, AdakrposUnauthContext } from "./types";
4
+
5
+ const UNAUTH_CONTEXT: AdakrposUnauthContext = {
6
+ user: null,
7
+ session: null,
8
+ isAuthenticated: false,
9
+ };
10
+
11
+ function getSessionId(cookieHeader: string): string | null {
12
+ const sessionMatch = cookieHeader.match(/(?:^|;\s*)adakrpos_session=([^;]+)/);
13
+
14
+ if (!sessionMatch) {
15
+ return null;
16
+ }
17
+
18
+ try {
19
+ return decodeURIComponent(sessionMatch[1]);
20
+ } catch {
21
+ return sessionMatch[1];
22
+ }
23
+ }
24
+
25
+ // Framework-agnostic helper using Web standard Request
26
+ // Works with CF Workers, Deno, Bun, and any Web standard environment
27
+ export async function verifyRequest(
28
+ request: Request,
29
+ config: AdakrposAuthConfig,
30
+ ): Promise<AuthContext> {
31
+ const client = createAdakrposAuth(config);
32
+
33
+ const cookieHeader = request.headers.get("Cookie") ?? "";
34
+ const sessionId = getSessionId(cookieHeader);
35
+
36
+ if (!sessionId) {
37
+ return UNAUTH_CONTEXT;
38
+ }
39
+
40
+ const result = await client.verifySession(sessionId);
41
+ if (!result) {
42
+ return UNAUTH_CONTEXT;
43
+ }
44
+
45
+ return {
46
+ user: result.user,
47
+ session: result.session,
48
+ isAuthenticated: true,
49
+ } satisfies AdakrposAuthContext;
50
+ }
package/src/hono.ts ADDED
@@ -0,0 +1,100 @@
1
+ import type { Context } from "hono";
2
+ import { createMiddleware } from "hono/factory";
3
+
4
+ import { createAdakrposAuth } from "./client";
5
+ import type { AdakrposAuthClient, AdakrposAuthConfig } from "./client";
6
+ import type { AdakrposAuthContext, AuthContext, AdakrposUnauthContext } from "./types";
7
+
8
+ type AuthFn = () => Promise<AuthContext>;
9
+
10
+ declare module "hono" {
11
+ interface ContextVariableMap {
12
+ auth: AuthFn;
13
+ }
14
+ }
15
+
16
+ const UNAUTH_CONTEXT: AdakrposUnauthContext = {
17
+ user: null,
18
+ session: null,
19
+ isAuthenticated: false,
20
+ };
21
+
22
+ function getSessionId(cookieHeader: string): string | null {
23
+ const sessionMatch = cookieHeader.match(/(?:^|;\s*)adakrpos_session=([^;]+)/);
24
+
25
+ if (!sessionMatch) {
26
+ return null;
27
+ }
28
+
29
+ try {
30
+ return decodeURIComponent(sessionMatch[1]);
31
+ } catch {
32
+ return sessionMatch[1];
33
+ }
34
+ }
35
+
36
+ function createAuthFn(client: AdakrposAuthClient, sessionId: string | null): AuthFn {
37
+ let authPromise: Promise<AuthContext> | undefined;
38
+
39
+ return async () => {
40
+ if (!authPromise) {
41
+ authPromise = (async () => {
42
+ if (!sessionId) {
43
+ return UNAUTH_CONTEXT;
44
+ }
45
+
46
+ const result = await client.verifySession(sessionId);
47
+
48
+ if (!result) {
49
+ return UNAUTH_CONTEXT;
50
+ }
51
+
52
+ return {
53
+ user: result.user,
54
+ session: result.session,
55
+ isAuthenticated: true,
56
+ } satisfies AdakrposAuthContext;
57
+ })();
58
+ }
59
+
60
+ return authPromise;
61
+ };
62
+ }
63
+
64
+ function setAuthContext(c: Context, client: AdakrposAuthClient): void {
65
+ const cookieHeader = c.req.header("Cookie") ?? "";
66
+ c.set("auth", createAuthFn(client, getSessionId(cookieHeader)));
67
+ }
68
+
69
+ export function adakrposAuth(config: AdakrposAuthConfig) {
70
+ const client = createAdakrposAuth(config);
71
+
72
+ return createMiddleware(async (c, next) => {
73
+ setAuthContext(c, client);
74
+ await next();
75
+ });
76
+ }
77
+
78
+ export async function getAuth(c: Context): Promise<AuthContext> {
79
+ const authFn = c.get("auth") as AuthFn | undefined;
80
+
81
+ if (!authFn) {
82
+ return UNAUTH_CONTEXT;
83
+ }
84
+
85
+ return authFn();
86
+ }
87
+
88
+ export function requireAuth(config: AdakrposAuthConfig) {
89
+ const client = createAdakrposAuth(config);
90
+
91
+ return createMiddleware(async (c, next) => {
92
+ setAuthContext(c, client);
93
+
94
+ if (!(await getAuth(c)).isAuthenticated) {
95
+ return c.json({ error: "Unauthorized" }, 401);
96
+ }
97
+
98
+ await next();
99
+ });
100
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./types";
2
+ export { createAdakrposAuth } from "./client";
3
+ export type { AdakrposAuthClient, AdakrposAuthConfig } from "./client";
4
+ export {
5
+ clearApiKeyCache,
6
+ getCachedApiKeyValidity,
7
+ setCachedApiKeyValidity,
8
+ } from "./cache";
package/src/types.ts ADDED
@@ -0,0 +1,54 @@
1
+ export interface AdakrposUser {
2
+ id: string;
3
+ email: string | null; // Apple email (apple_email column)
4
+ verifiedEmail: string | null; // pos.idserve.net verified email
5
+ nickname: string | null;
6
+ name: string | null;
7
+ profilePhotoUrl: string | null;
8
+ bio: string | null;
9
+ contact: string | null;
10
+ snsLinks: Record<string, string>;
11
+ isVerified: boolean; // true if pos.idserve.net email verified
12
+ createdAt: number; // Unix timestamp (milliseconds)
13
+ updatedAt: number; // Unix timestamp (milliseconds)
14
+ }
15
+
16
+ export interface AdakrposSession {
17
+ id: string;
18
+ userId: string;
19
+ expiresAt: number; // Unix timestamp (milliseconds)
20
+ createdAt: number; // Unix timestamp (milliseconds)
21
+ }
22
+
23
+ export interface AdakrposAuthContext {
24
+ user: AdakrposUser;
25
+ session: AdakrposSession;
26
+ isAuthenticated: true;
27
+ }
28
+
29
+ export interface AdakrposUnauthContext {
30
+ user: null;
31
+ session: null;
32
+ isAuthenticated: false;
33
+ }
34
+
35
+ export type AuthContext = AdakrposAuthContext | AdakrposUnauthContext;
36
+
37
+ export interface DeveloperApp {
38
+ id: string;
39
+ userId: string;
40
+ name: string;
41
+ description: string | null;
42
+ apiKeyPrefix: string; // First 8 chars only (NEVER full key)
43
+ redirectUrls: string[];
44
+ isActive: boolean;
45
+ createdAt: number;
46
+ updatedAt: number;
47
+ }
48
+
49
+ export interface ApiKeyInfo {
50
+ appId: string;
51
+ userId: string;
52
+ prefix: string;
53
+ isActive: boolean;
54
+ }