organify-auth-sdk 0.2.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.
@@ -0,0 +1,158 @@
1
+ import jwt from 'jsonwebtoken';
2
+ import { encode, decode } from '@msgpack/msgpack';
3
+
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __commonJS = (cb, mod) => function __require() {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __decorateClass = (decorators, target, key, kind) => {
30
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
31
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
32
+ if (decorator = decorators[i])
33
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
34
+ if (kind && result) __defProp(target, key, result);
35
+ return result;
36
+ };
37
+ function verifyToken(token, options) {
38
+ return jwt.verify(token, options.secret, {
39
+ ignoreExpiration: options.ignoreExpiration
40
+ });
41
+ }
42
+ function decodeToken(token) {
43
+ return jwt.decode(token);
44
+ }
45
+ function extractBearerToken(authHeader) {
46
+ if (!authHeader?.startsWith("Bearer ")) return null;
47
+ return authHeader.substring(7);
48
+ }
49
+ function extractCookieToken(cookieHeader, cookieName = "auth_token") {
50
+ if (!cookieHeader) return null;
51
+ const match = cookieHeader.split(";").map((s) => s.trim()).find((s) => s.startsWith(`${cookieName}=`));
52
+ if (!match) return null;
53
+ return decodeURIComponent(match.split("=")[1]);
54
+ }
55
+ function hasPermission(role, permission, rolePermissions) {
56
+ const perms = rolePermissions[role];
57
+ if (!perms) return false;
58
+ return perms.includes(permission);
59
+ }
60
+ function validateServiceToken(token, expectedToken) {
61
+ if (!token) return false;
62
+ return token === expectedToken;
63
+ }
64
+ var MSGPACK_MIME = "application/x-msgpack";
65
+ var ServiceClient = class {
66
+ config;
67
+ constructor(config) {
68
+ this.config = {
69
+ serviceToken: config.serviceToken,
70
+ timeout: config.timeout ?? 1e4,
71
+ headers: config.headers ?? {}
72
+ };
73
+ }
74
+ // ─── HTTP Methods ─────────────────────────
75
+ async get(url, opts) {
76
+ return this.request("GET", url, void 0, opts);
77
+ }
78
+ async post(url, body, opts) {
79
+ return this.request("POST", url, body, opts);
80
+ }
81
+ async put(url, body, opts) {
82
+ return this.request("PUT", url, body, opts);
83
+ }
84
+ async patch(url, body, opts) {
85
+ return this.request("PATCH", url, body, opts);
86
+ }
87
+ async delete(url, opts) {
88
+ return this.request("DELETE", url, void 0, opts);
89
+ }
90
+ // ─── Core Request ─────────────────────────
91
+ async request(method, url, body, opts) {
92
+ const timeout = opts?.timeout ?? this.config.timeout;
93
+ const controller = new AbortController();
94
+ const timer = setTimeout(() => controller.abort(), timeout);
95
+ try {
96
+ const headers = {
97
+ // Always use MessagePack for internal communication
98
+ "Accept": MSGPACK_MIME,
99
+ "X-Service-Token": this.config.serviceToken,
100
+ ...this.config.headers,
101
+ ...opts?.headers
102
+ };
103
+ if (opts?.userId) {
104
+ headers["x-user-id"] = opts.userId;
105
+ }
106
+ const fetchOpts = {
107
+ method,
108
+ headers,
109
+ signal: controller.signal
110
+ };
111
+ if (body !== void 0 && body !== null) {
112
+ const packed = encode(body);
113
+ fetchOpts.body = Buffer.from(packed.buffer, packed.byteOffset, packed.byteLength);
114
+ headers["Content-Type"] = MSGPACK_MIME;
115
+ }
116
+ const response = await fetch(url, fetchOpts);
117
+ const contentType = response.headers.get("content-type") || "";
118
+ let data;
119
+ if (/msgpack/i.test(contentType)) {
120
+ const buffer = await response.arrayBuffer();
121
+ data = decode(new Uint8Array(buffer));
122
+ } else {
123
+ const text = await response.text();
124
+ try {
125
+ data = JSON.parse(text);
126
+ } catch {
127
+ data = text;
128
+ }
129
+ }
130
+ const responseHeaders = {};
131
+ response.headers.forEach((value, key) => {
132
+ responseHeaders[key] = value;
133
+ });
134
+ if (!response.ok) {
135
+ throw new ServiceClientError(
136
+ `Service request failed: ${method} ${url} \u2192 ${response.status}`,
137
+ response.status,
138
+ data
139
+ );
140
+ }
141
+ return { data, status: response.status, headers: responseHeaders };
142
+ } finally {
143
+ clearTimeout(timer);
144
+ }
145
+ }
146
+ };
147
+ var ServiceClientError = class extends Error {
148
+ constructor(message, status, response) {
149
+ super(message);
150
+ this.status = status;
151
+ this.response = response;
152
+ this.name = "ServiceClientError";
153
+ }
154
+ };
155
+
156
+ export { MSGPACK_MIME, ServiceClient, ServiceClientError, __commonJS, __decorateClass, __toESM, decodeToken, extractBearerToken, extractCookieToken, hasPermission, validateServiceToken, verifyToken };
157
+ //# sourceMappingURL=chunk-M5JNBLJQ.js.map
158
+ //# sourceMappingURL=chunk-M5JNBLJQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/jwt.ts","../src/service-client.ts"],"names":[],"mappings":"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","file":"chunk-M5JNBLJQ.js","sourcesContent":["// ─────────────────────────────────────────────\n// @organify/auth-sdk — JWT verification helpers\n// ─────────────────────────────────────────────\n\nimport jwt from 'jsonwebtoken';\nimport type { JwtPayload as OrganifyJwtPayload } from './types';\n\nexport interface VerifyOptions {\n secret: string;\n /** When true, expired tokens won't throw */\n ignoreExpiration?: boolean;\n}\n\n/**\n * Verify and decode a JWT token.\n * @throws Error if token is invalid or expired\n */\nexport function verifyToken(\n token: string,\n options: VerifyOptions,\n): OrganifyJwtPayload {\n return jwt.verify(token, options.secret, {\n ignoreExpiration: options.ignoreExpiration,\n }) as unknown as OrganifyJwtPayload;\n}\n\n/**\n * Decode a JWT without verifying (useful for debugging).\n */\nexport function decodeToken(token: string): OrganifyJwtPayload | null {\n return jwt.decode(token) as OrganifyJwtPayload | null;\n}\n\n/**\n * Extract Bearer token from Authorization header.\n */\nexport function extractBearerToken(\n authHeader: string | undefined,\n): string | null {\n if (!authHeader?.startsWith('Bearer ')) return null;\n return authHeader.substring(7);\n}\n\n/**\n * Extract a token from a cookie header string.\n */\nexport function extractCookieToken(\n cookieHeader: string | undefined,\n cookieName = 'auth_token',\n): string | null {\n if (!cookieHeader) return null;\n const match = cookieHeader\n .split(';')\n .map((s) => s.trim())\n .find((s) => s.startsWith(`${cookieName}=`));\n if (!match) return null;\n return decodeURIComponent(match.split('=')[1]);\n}\n\n/**\n * Check if a user has a specific permission based on their role.\n */\nexport function hasPermission(\n role: string,\n permission: string,\n rolePermissions: Record<string, string[]>,\n): boolean {\n const perms = rolePermissions[role];\n if (!perms) return false;\n return perms.includes(permission);\n}\n\n/**\n * Validate a service-to-service token.\n */\nexport function validateServiceToken(\n token: string | undefined,\n expectedToken: string,\n): boolean {\n if (!token) return false;\n return token === expectedToken;\n}\n","// ─────────────────────────────────────────────\n// ServiceClient — Inter-Service HTTP Client with MessagePack\n// ─────────────────────────────────────────────\n// All service-to-service communication uses MessagePack for\n// serialization/deserialization. This client:\n//\n// 1. Sends request bodies as MessagePack (Content-Type: application/x-msgpack)\n// 2. Requests MessagePack responses (Accept: application/x-msgpack)\n// 3. Injects X-Service-Token for authentication\n// 4. Automatically decodes MessagePack responses\n// 5. Falls back to JSON when MessagePack is not available\n//\n// Usage:\n// const client = new ServiceClient({\n// serviceToken: 'shared-secret',\n// });\n// const users = await client.get<User[]>('http://users:4001/api/internal/users/batch');\n// const workspace = await client.post('http://workspaces:4002/api/internal/workspaces/personal', { userId: '1', userName: 'John' });\n// ─────────────────────────────────────────────\n\nimport { encode, decode } from '@msgpack/msgpack';\n\nexport const MSGPACK_MIME = 'application/x-msgpack';\n\nexport interface ServiceClientConfig {\n /** Shared service-to-service token (SERVICE_TO_SERVICE_TOKEN) */\n serviceToken: string;\n /** Default timeout in ms (default: 10000) */\n timeout?: number;\n /** Additional default headers */\n headers?: Record<string, string>;\n}\n\nexport interface ServiceRequestOptions {\n /** Override timeout for this request */\n timeout?: number;\n /** Additional headers for this request */\n headers?: Record<string, string>;\n /** Override user ID to forward downstream */\n userId?: string;\n}\n\nexport interface ServiceResponse<T = any> {\n data: T;\n status: number;\n headers: Record<string, string>;\n}\n\nexport class ServiceClient {\n private config: Required<Pick<ServiceClientConfig, 'serviceToken' | 'timeout'>> & {\n headers: Record<string, string>;\n };\n\n constructor(config: ServiceClientConfig) {\n this.config = {\n serviceToken: config.serviceToken,\n timeout: config.timeout ?? 10_000,\n headers: config.headers ?? {},\n };\n }\n\n // ─── HTTP Methods ─────────────────────────\n\n async get<T = any>(url: string, opts?: ServiceRequestOptions): Promise<ServiceResponse<T>> {\n return this.request<T>('GET', url, undefined, opts);\n }\n\n async post<T = any>(url: string, body?: any, opts?: ServiceRequestOptions): Promise<ServiceResponse<T>> {\n return this.request<T>('POST', url, body, opts);\n }\n\n async put<T = any>(url: string, body?: any, opts?: ServiceRequestOptions): Promise<ServiceResponse<T>> {\n return this.request<T>('PUT', url, body, opts);\n }\n\n async patch<T = any>(url: string, body?: any, opts?: ServiceRequestOptions): Promise<ServiceResponse<T>> {\n return this.request<T>('PATCH', url, body, opts);\n }\n\n async delete<T = any>(url: string, opts?: ServiceRequestOptions): Promise<ServiceResponse<T>> {\n return this.request<T>('DELETE', url, undefined, opts);\n }\n\n // ─── Core Request ─────────────────────────\n\n private async request<T>(\n method: string,\n url: string,\n body?: any,\n opts?: ServiceRequestOptions,\n ): Promise<ServiceResponse<T>> {\n const timeout = opts?.timeout ?? this.config.timeout;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n\n try {\n const headers: Record<string, string> = {\n // Always use MessagePack for internal communication\n 'Accept': MSGPACK_MIME,\n 'X-Service-Token': this.config.serviceToken,\n ...this.config.headers,\n ...opts?.headers,\n };\n\n // Forward user identity if provided\n if (opts?.userId) {\n headers['x-user-id'] = opts.userId;\n }\n\n const fetchOpts: RequestInit = {\n method,\n headers,\n signal: controller.signal,\n };\n\n // Encode body as MessagePack if present\n if (body !== undefined && body !== null) {\n const packed = encode(body);\n fetchOpts.body = Buffer.from(packed.buffer, packed.byteOffset, packed.byteLength);\n headers['Content-Type'] = MSGPACK_MIME;\n }\n\n const response = await fetch(url, fetchOpts);\n\n // Parse response based on Content-Type\n const contentType = response.headers.get('content-type') || '';\n let data: T;\n\n if (/msgpack/i.test(contentType)) {\n const buffer = await response.arrayBuffer();\n data = decode(new Uint8Array(buffer)) as T;\n } else {\n // Fallback to JSON\n const text = await response.text();\n try {\n data = JSON.parse(text) as T;\n } catch {\n data = text as unknown as T;\n }\n }\n\n // Collect response headers\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n if (!response.ok) {\n throw new ServiceClientError(\n `Service request failed: ${method} ${url} → ${response.status}`,\n response.status,\n data,\n );\n }\n\n return { data, status: response.status, headers: responseHeaders };\n } finally {\n clearTimeout(timer);\n }\n }\n}\n\n// ─── Error Class ────────────────────────────\n\nexport class ServiceClientError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly response: any,\n ) {\n super(message);\n this.name = 'ServiceClientError';\n }\n}\n"]}
@@ -0,0 +1,152 @@
1
+ interface JwtPayload {
2
+ /** User ID (subject) */
3
+ sub: number;
4
+ email: string;
5
+ name: string;
6
+ role: 'admin' | 'user' | 'manager';
7
+ plan: string;
8
+ teamIds: number[];
9
+ /** Token type — only present on refresh tokens */
10
+ tokenType?: 'refresh';
11
+ iat?: number;
12
+ exp?: number;
13
+ }
14
+ interface AuthenticatedUser {
15
+ userId: number;
16
+ email: string;
17
+ name: string;
18
+ role: string;
19
+ plan: string;
20
+ }
21
+ interface JwtTokens {
22
+ accessToken: string;
23
+ refreshToken: string;
24
+ }
25
+ declare enum Permission {
26
+ READ_OWN_PROFILE = "read:own_profile",
27
+ UPDATE_OWN_PROFILE = "update:own_profile",
28
+ READ_USERS = "read:users",
29
+ CREATE_USERS = "create:users",
30
+ UPDATE_USERS = "update:users",
31
+ DELETE_USERS = "delete:users",
32
+ READ_PROJECTS = "read:projects",
33
+ CREATE_PROJECTS = "create:projects",
34
+ UPDATE_PROJECTS = "update:projects",
35
+ DELETE_PROJECTS = "delete:projects",
36
+ READ_TASKS = "read:tasks",
37
+ CREATE_TASKS = "create:tasks",
38
+ UPDATE_TASKS = "update:tasks",
39
+ DELETE_TASKS = "delete:tasks",
40
+ READ_TEAMS = "read:teams",
41
+ CREATE_TEAMS = "create:teams",
42
+ UPDATE_TEAMS = "update:teams",
43
+ DELETE_TEAMS = "delete:teams",
44
+ MANAGE_TEAM_MEMBERS = "manage:team_members",
45
+ READ_REPORTS = "read:reports",
46
+ CREATE_REPORTS = "create:reports",
47
+ USE_AI = "use:ai",
48
+ READ_PLANS = "read:plans",
49
+ CREATE_PLANS = "create:plans",
50
+ UPDATE_PLANS = "update:plans",
51
+ DELETE_PLANS = "delete:plans",
52
+ MANAGE_INTEGRATIONS = "manage:integrations",
53
+ MANAGE_AUTOMATIONS = "manage:automations",
54
+ VIEW_ANALYTICS = "view:analytics"
55
+ }
56
+ declare const RolePermissions: Record<string, Permission[]>;
57
+
58
+ interface VerifyOptions {
59
+ secret: string;
60
+ /** When true, expired tokens won't throw */
61
+ ignoreExpiration?: boolean;
62
+ }
63
+ /**
64
+ * Verify and decode a JWT token.
65
+ * @throws Error if token is invalid or expired
66
+ */
67
+ declare function verifyToken(token: string, options: VerifyOptions): JwtPayload;
68
+ /**
69
+ * Decode a JWT without verifying (useful for debugging).
70
+ */
71
+ declare function decodeToken(token: string): JwtPayload | null;
72
+ /**
73
+ * Extract Bearer token from Authorization header.
74
+ */
75
+ declare function extractBearerToken(authHeader: string | undefined): string | null;
76
+ /**
77
+ * Extract a token from a cookie header string.
78
+ */
79
+ declare function extractCookieToken(cookieHeader: string | undefined, cookieName?: string): string | null;
80
+ /**
81
+ * Check if a user has a specific permission based on their role.
82
+ */
83
+ declare function hasPermission(role: string, permission: string, rolePermissions: Record<string, string[]>): boolean;
84
+ /**
85
+ * Validate a service-to-service token.
86
+ */
87
+ declare function validateServiceToken(token: string | undefined, expectedToken: string): boolean;
88
+
89
+ /**
90
+ * Get the current FBToken for API requests.
91
+ * Call this on every request — it caches within the same 30-min window.
92
+ *
93
+ * @param secret - The shared FB_TOKEN_SECRET. If not provided,
94
+ * reads from NEXT_PUBLIC_FB_TOKEN_SECRET or VITE_FB_TOKEN_SECRET.
95
+ * @returns The HMAC-SHA512 hex string to send in X-FB-Token header.
96
+ */
97
+ declare function getFBToken(secret?: string): Promise<string>;
98
+ /**
99
+ * Create an Axios/fetch interceptor that automatically adds X-FB-Token.
100
+ *
101
+ * Usage with fetch:
102
+ * const headers = await createFBTokenHeaders();
103
+ * fetch(url, { headers: { ...headers, ...otherHeaders } });
104
+ *
105
+ * Usage with Axios:
106
+ * axios.interceptors.request.use(fbTokenAxiosInterceptor(secret));
107
+ */
108
+ declare function createFBTokenHeaders(secret?: string): Promise<Record<string, string>>;
109
+ /**
110
+ * Axios request interceptor factory.
111
+ */
112
+ declare function fbTokenAxiosInterceptor(secret?: string): (config: any) => Promise<any>;
113
+
114
+ declare const MSGPACK_MIME = "application/x-msgpack";
115
+ interface ServiceClientConfig {
116
+ /** Shared service-to-service token (SERVICE_TO_SERVICE_TOKEN) */
117
+ serviceToken: string;
118
+ /** Default timeout in ms (default: 10000) */
119
+ timeout?: number;
120
+ /** Additional default headers */
121
+ headers?: Record<string, string>;
122
+ }
123
+ interface ServiceRequestOptions {
124
+ /** Override timeout for this request */
125
+ timeout?: number;
126
+ /** Additional headers for this request */
127
+ headers?: Record<string, string>;
128
+ /** Override user ID to forward downstream */
129
+ userId?: string;
130
+ }
131
+ interface ServiceResponse<T = any> {
132
+ data: T;
133
+ status: number;
134
+ headers: Record<string, string>;
135
+ }
136
+ declare class ServiceClient {
137
+ private config;
138
+ constructor(config: ServiceClientConfig);
139
+ get<T = any>(url: string, opts?: ServiceRequestOptions): Promise<ServiceResponse<T>>;
140
+ post<T = any>(url: string, body?: any, opts?: ServiceRequestOptions): Promise<ServiceResponse<T>>;
141
+ put<T = any>(url: string, body?: any, opts?: ServiceRequestOptions): Promise<ServiceResponse<T>>;
142
+ patch<T = any>(url: string, body?: any, opts?: ServiceRequestOptions): Promise<ServiceResponse<T>>;
143
+ delete<T = any>(url: string, opts?: ServiceRequestOptions): Promise<ServiceResponse<T>>;
144
+ private request;
145
+ }
146
+ declare class ServiceClientError extends Error {
147
+ readonly status: number;
148
+ readonly response: any;
149
+ constructor(message: string, status: number, response: any);
150
+ }
151
+
152
+ export { type AuthenticatedUser, type JwtPayload, type JwtTokens, MSGPACK_MIME, Permission, RolePermissions, ServiceClient, type ServiceClientConfig, ServiceClientError, type ServiceRequestOptions, type ServiceResponse, type VerifyOptions, createFBTokenHeaders, decodeToken, extractBearerToken, extractCookieToken, fbTokenAxiosInterceptor, getFBToken, hasPermission, validateServiceToken, verifyToken };
package/dist/index.js ADDED
@@ -0,0 +1,132 @@
1
+ export { MSGPACK_MIME, ServiceClient, ServiceClientError, decodeToken, extractBearerToken, extractCookieToken, hasPermission, validateServiceToken, verifyToken } from './chunk-M5JNBLJQ.js';
2
+
3
+ // src/types.ts
4
+ var Permission = /* @__PURE__ */ ((Permission2) => {
5
+ Permission2["READ_OWN_PROFILE"] = "read:own_profile";
6
+ Permission2["UPDATE_OWN_PROFILE"] = "update:own_profile";
7
+ Permission2["READ_USERS"] = "read:users";
8
+ Permission2["CREATE_USERS"] = "create:users";
9
+ Permission2["UPDATE_USERS"] = "update:users";
10
+ Permission2["DELETE_USERS"] = "delete:users";
11
+ Permission2["READ_PROJECTS"] = "read:projects";
12
+ Permission2["CREATE_PROJECTS"] = "create:projects";
13
+ Permission2["UPDATE_PROJECTS"] = "update:projects";
14
+ Permission2["DELETE_PROJECTS"] = "delete:projects";
15
+ Permission2["READ_TASKS"] = "read:tasks";
16
+ Permission2["CREATE_TASKS"] = "create:tasks";
17
+ Permission2["UPDATE_TASKS"] = "update:tasks";
18
+ Permission2["DELETE_TASKS"] = "delete:tasks";
19
+ Permission2["READ_TEAMS"] = "read:teams";
20
+ Permission2["CREATE_TEAMS"] = "create:teams";
21
+ Permission2["UPDATE_TEAMS"] = "update:teams";
22
+ Permission2["DELETE_TEAMS"] = "delete:teams";
23
+ Permission2["MANAGE_TEAM_MEMBERS"] = "manage:team_members";
24
+ Permission2["READ_REPORTS"] = "read:reports";
25
+ Permission2["CREATE_REPORTS"] = "create:reports";
26
+ Permission2["USE_AI"] = "use:ai";
27
+ Permission2["READ_PLANS"] = "read:plans";
28
+ Permission2["CREATE_PLANS"] = "create:plans";
29
+ Permission2["UPDATE_PLANS"] = "update:plans";
30
+ Permission2["DELETE_PLANS"] = "delete:plans";
31
+ Permission2["MANAGE_INTEGRATIONS"] = "manage:integrations";
32
+ Permission2["MANAGE_AUTOMATIONS"] = "manage:automations";
33
+ Permission2["VIEW_ANALYTICS"] = "view:analytics";
34
+ return Permission2;
35
+ })(Permission || {});
36
+ var RolePermissions = {
37
+ admin: [...Object.values(Permission)],
38
+ manager: [
39
+ "read:own_profile" /* READ_OWN_PROFILE */,
40
+ "update:own_profile" /* UPDATE_OWN_PROFILE */,
41
+ "read:projects" /* READ_PROJECTS */,
42
+ "create:projects" /* CREATE_PROJECTS */,
43
+ "update:projects" /* UPDATE_PROJECTS */,
44
+ "read:tasks" /* READ_TASKS */,
45
+ "create:tasks" /* CREATE_TASKS */,
46
+ "update:tasks" /* UPDATE_TASKS */,
47
+ "delete:tasks" /* DELETE_TASKS */,
48
+ "read:teams" /* READ_TEAMS */,
49
+ "create:teams" /* CREATE_TEAMS */,
50
+ "update:teams" /* UPDATE_TEAMS */,
51
+ "manage:team_members" /* MANAGE_TEAM_MEMBERS */,
52
+ "read:reports" /* READ_REPORTS */,
53
+ "create:reports" /* CREATE_REPORTS */,
54
+ "use:ai" /* USE_AI */,
55
+ "view:analytics" /* VIEW_ANALYTICS */
56
+ ],
57
+ user: [
58
+ "read:own_profile" /* READ_OWN_PROFILE */,
59
+ "update:own_profile" /* UPDATE_OWN_PROFILE */,
60
+ "read:projects" /* READ_PROJECTS */,
61
+ "create:projects" /* CREATE_PROJECTS */,
62
+ "update:projects" /* UPDATE_PROJECTS */,
63
+ "read:tasks" /* READ_TASKS */,
64
+ "create:tasks" /* CREATE_TASKS */,
65
+ "update:tasks" /* UPDATE_TASKS */,
66
+ "delete:tasks" /* DELETE_TASKS */,
67
+ "read:teams" /* READ_TEAMS */,
68
+ "create:teams" /* CREATE_TEAMS */,
69
+ "read:reports" /* READ_REPORTS */,
70
+ "use:ai" /* USE_AI */
71
+ ]
72
+ };
73
+
74
+ // src/fb-token.ts
75
+ var WINDOW_MS = 30 * 60 * 1e3;
76
+ var cachedToken = null;
77
+ var cachedWindowIndex = -1;
78
+ function getTimeWindowIndex() {
79
+ return Math.floor(Date.now() / WINDOW_MS);
80
+ }
81
+ async function hmacSHA512(secret, message) {
82
+ const encoder = new TextEncoder();
83
+ const keyData = encoder.encode(secret);
84
+ const msgData = encoder.encode(message);
85
+ const key = await crypto.subtle.importKey(
86
+ "raw",
87
+ keyData,
88
+ { name: "HMAC", hash: "SHA-512" },
89
+ false,
90
+ ["sign"]
91
+ );
92
+ const signature = await crypto.subtle.sign("HMAC", key, msgData);
93
+ const bytes = new Uint8Array(signature);
94
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
95
+ }
96
+ async function getFBToken(secret) {
97
+ const windowIndex = getTimeWindowIndex();
98
+ if (cachedToken && cachedWindowIndex === windowIndex) {
99
+ return cachedToken;
100
+ }
101
+ const resolvedSecret = secret || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_FB_TOKEN_SECRET || typeof import.meta !== "undefined" && import.meta?.env?.VITE_FB_TOKEN_SECRET || "";
102
+ if (!resolvedSecret) {
103
+ console.warn("[FBToken] No FB_TOKEN_SECRET configured");
104
+ return "";
105
+ }
106
+ const token = await hmacSHA512(
107
+ resolvedSecret,
108
+ `organify-fb-v1:${windowIndex}`
109
+ );
110
+ cachedToken = token;
111
+ cachedWindowIndex = windowIndex;
112
+ return token;
113
+ }
114
+ async function createFBTokenHeaders(secret) {
115
+ const token = await getFBToken(secret);
116
+ if (!token) return {};
117
+ return { "X-FB-Token": token };
118
+ }
119
+ function fbTokenAxiosInterceptor(secret) {
120
+ return async (config) => {
121
+ const token = await getFBToken(secret);
122
+ if (token) {
123
+ config.headers = config.headers || {};
124
+ config.headers["X-FB-Token"] = token;
125
+ }
126
+ return config;
127
+ };
128
+ }
129
+
130
+ export { Permission, RolePermissions, createFBTokenHeaders, fbTokenAxiosInterceptor, getFBToken };
131
+ //# sourceMappingURL=index.js.map
132
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/fb-token.ts"],"names":["Permission"],"mappings":"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","file":"index.js","sourcesContent":["// ─────────────────────────────────────────────\n// @organify/auth-sdk — Shared Auth Types\n// ─────────────────────────────────────────────\n\nexport interface JwtPayload {\n /** User ID (subject) */\n sub: number;\n email: string;\n name: string;\n role: 'admin' | 'user' | 'manager';\n plan: string;\n teamIds: number[];\n /** Token type — only present on refresh tokens */\n tokenType?: 'refresh';\n iat?: number;\n exp?: number;\n}\n\nexport interface AuthenticatedUser {\n userId: number;\n email: string;\n name: string;\n role: string;\n plan: string;\n}\n\nexport interface JwtTokens {\n accessToken: string;\n refreshToken: string;\n}\n\n// ─── Permissions ────────────────────────────\n\nexport enum Permission {\n READ_OWN_PROFILE = 'read:own_profile',\n UPDATE_OWN_PROFILE = 'update:own_profile',\n READ_USERS = 'read:users',\n CREATE_USERS = 'create:users',\n UPDATE_USERS = 'update:users',\n DELETE_USERS = 'delete:users',\n READ_PROJECTS = 'read:projects',\n CREATE_PROJECTS = 'create:projects',\n UPDATE_PROJECTS = 'update:projects',\n DELETE_PROJECTS = 'delete:projects',\n READ_TASKS = 'read:tasks',\n CREATE_TASKS = 'create:tasks',\n UPDATE_TASKS = 'update:tasks',\n DELETE_TASKS = 'delete:tasks',\n READ_TEAMS = 'read:teams',\n CREATE_TEAMS = 'create:teams',\n UPDATE_TEAMS = 'update:teams',\n DELETE_TEAMS = 'delete:teams',\n MANAGE_TEAM_MEMBERS = 'manage:team_members',\n READ_REPORTS = 'read:reports',\n CREATE_REPORTS = 'create:reports',\n USE_AI = 'use:ai',\n READ_PLANS = 'read:plans',\n CREATE_PLANS = 'create:plans',\n UPDATE_PLANS = 'update:plans',\n DELETE_PLANS = 'delete:plans',\n MANAGE_INTEGRATIONS = 'manage:integrations',\n MANAGE_AUTOMATIONS = 'manage:automations',\n VIEW_ANALYTICS = 'view:analytics',\n}\n\nexport const RolePermissions: Record<string, Permission[]> = {\n admin: [...Object.values(Permission)],\n manager: [\n Permission.READ_OWN_PROFILE,\n Permission.UPDATE_OWN_PROFILE,\n Permission.READ_PROJECTS,\n Permission.CREATE_PROJECTS,\n Permission.UPDATE_PROJECTS,\n Permission.READ_TASKS,\n Permission.CREATE_TASKS,\n Permission.UPDATE_TASKS,\n Permission.DELETE_TASKS,\n Permission.READ_TEAMS,\n Permission.CREATE_TEAMS,\n Permission.UPDATE_TEAMS,\n Permission.MANAGE_TEAM_MEMBERS,\n Permission.READ_REPORTS,\n Permission.CREATE_REPORTS,\n Permission.USE_AI,\n Permission.VIEW_ANALYTICS,\n ],\n user: [\n Permission.READ_OWN_PROFILE,\n Permission.UPDATE_OWN_PROFILE,\n Permission.READ_PROJECTS,\n Permission.CREATE_PROJECTS,\n Permission.UPDATE_PROJECTS,\n Permission.READ_TASKS,\n Permission.CREATE_TASKS,\n Permission.UPDATE_TASKS,\n Permission.DELETE_TASKS,\n Permission.READ_TEAMS,\n Permission.CREATE_TEAMS,\n Permission.READ_REPORTS,\n Permission.USE_AI,\n ],\n};\n","// ─────────────────────────────────────────────\n// FBToken Client — Front-to-Back Token for API Requests\n// ─────────────────────────────────────────────\n// Computes HMAC-SHA512(secret, timeWindow) matching the gateway.\n// Every 30 minutes the token rotates.\n//\n// Usage in frontend:\n// import { getFBToken } from '@organify/auth-sdk/fb-token';\n// headers['X-FB-Token'] = getFBToken();\n//\n// Usage in Web Components:\n// Same import, same usage.\n//\n// The secret comes from an environment variable:\n// NEXT_PUBLIC_FB_TOKEN_SECRET (Next.js)\n// VITE_FB_TOKEN_SECRET (Vite)\n// ─────────────────────────────────────────────\n\n/** Duration of each time window in milliseconds (30 minutes) */\nconst WINDOW_MS = 30 * 60 * 1000;\n\n/** Cache: avoid recomputing on every request within same window */\nlet cachedToken: string | null = null;\nlet cachedWindowIndex = -1;\n\n/**\n * Compute the current time-window index (epoch / 30min).\n */\nfunction getTimeWindowIndex(): number {\n return Math.floor(Date.now() / WINDOW_MS);\n}\n\n/**\n * HMAC-SHA512 using Web Crypto API (works in browser + Edge + Node 18+).\n */\nasync function hmacSHA512(secret: string, message: string): Promise<string> {\n const encoder = new TextEncoder();\n const keyData = encoder.encode(secret);\n const msgData = encoder.encode(message);\n\n const key = await crypto.subtle.importKey(\n 'raw',\n keyData,\n { name: 'HMAC', hash: 'SHA-512' },\n false,\n ['sign'],\n );\n\n const signature = await crypto.subtle.sign('HMAC', key, msgData);\n const bytes = new Uint8Array(signature);\n\n // Convert to hex\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/**\n * Get the current FBToken for API requests.\n * Call this on every request — it caches within the same 30-min window.\n *\n * @param secret - The shared FB_TOKEN_SECRET. If not provided,\n * reads from NEXT_PUBLIC_FB_TOKEN_SECRET or VITE_FB_TOKEN_SECRET.\n * @returns The HMAC-SHA512 hex string to send in X-FB-Token header.\n */\nexport async function getFBToken(secret?: string): Promise<string> {\n const windowIndex = getTimeWindowIndex();\n\n // Return cached token if still in same window\n if (cachedToken && cachedWindowIndex === windowIndex) {\n return cachedToken;\n }\n\n const resolvedSecret =\n secret ||\n (typeof process !== 'undefined' && (process.env as any)?.NEXT_PUBLIC_FB_TOKEN_SECRET) ||\n (typeof import.meta !== 'undefined' && (import.meta as any)?.env?.VITE_FB_TOKEN_SECRET) ||\n '';\n\n if (!resolvedSecret) {\n console.warn('[FBToken] No FB_TOKEN_SECRET configured');\n return '';\n }\n\n const token = await hmacSHA512(\n resolvedSecret,\n `organify-fb-v1:${windowIndex}`,\n );\n\n cachedToken = token;\n cachedWindowIndex = windowIndex;\n\n return token;\n}\n\n/**\n * Create an Axios/fetch interceptor that automatically adds X-FB-Token.\n *\n * Usage with fetch:\n * const headers = await createFBTokenHeaders();\n * fetch(url, { headers: { ...headers, ...otherHeaders } });\n *\n * Usage with Axios:\n * axios.interceptors.request.use(fbTokenAxiosInterceptor(secret));\n */\nexport async function createFBTokenHeaders(secret?: string): Promise<Record<string, string>> {\n const token = await getFBToken(secret);\n if (!token) return {};\n return { 'X-FB-Token': token };\n}\n\n/**\n * Axios request interceptor factory.\n */\nexport function fbTokenAxiosInterceptor(secret?: string) {\n return async (config: any) => {\n const token = await getFBToken(secret);\n if (token) {\n config.headers = config.headers || {};\n config.headers['X-FB-Token'] = token;\n }\n return config;\n };\n}\n"]}
@@ -0,0 +1,106 @@
1
+ import * as _nestjs_common from '@nestjs/common';
2
+ import { CanActivate, ExecutionContext, NestInterceptor, CallHandler, DynamicModule } from '@nestjs/common';
3
+ import { Reflector } from '@nestjs/core';
4
+ import { ConfigService } from '@nestjs/config';
5
+ import { Observable } from 'rxjs';
6
+
7
+ /**
8
+ * Reusable JWT Auth Guard for any NestJS microservice.
9
+ * Validates JWT from Authorization header or auth_token cookie.
10
+ * Skips routes marked with @Public().
11
+ *
12
+ * Requires JWT_SECRET in ConfigService.
13
+ */
14
+ declare class AuthGuard implements CanActivate {
15
+ private reflector;
16
+ private config;
17
+ constructor(reflector: Reflector, config: ConfigService);
18
+ canActivate(context: ExecutionContext): boolean;
19
+ }
20
+
21
+ /**
22
+ * Guard for service-to-service authentication.
23
+ * Validates x-service-token / x-access-token header against
24
+ * SERVICE_TO_SERVICE_TOKEN env var.
25
+ */
26
+ declare class ServiceAuthGuard implements CanActivate {
27
+ private config;
28
+ constructor(config: ConfigService);
29
+ canActivate(context: ExecutionContext): boolean;
30
+ }
31
+
32
+ /**
33
+ * Reusable Roles Guard for any NestJS microservice.
34
+ * Checks request.user.role against @Roles() decorator.
35
+ */
36
+ declare class RolesGuard implements CanActivate {
37
+ private reflector;
38
+ constructor(reflector: Reflector);
39
+ canActivate(context: ExecutionContext): boolean;
40
+ }
41
+
42
+ /**
43
+ * Extract the authenticated user from request.
44
+ * Usage: @CurrentUser() user: AuthenticatedUser
45
+ */
46
+ declare const CurrentUser: (...dataOrPipes: unknown[]) => ParameterDecorator;
47
+
48
+ /**
49
+ * Extract the user ID from request headers.
50
+ * The API Gateway sets `x-user-id` after JWT decode.
51
+ *
52
+ * Usage (REST): @CurrentUserId() userId: string
53
+ * Usage (GQL): @CurrentUserId() userId: string
54
+ */
55
+ declare const CurrentUserId: (...dataOrPipes: unknown[]) => ParameterDecorator;
56
+
57
+ declare const IS_PUBLIC_KEY = "isPublic";
58
+ declare const Public: () => _nestjs_common.CustomDecorator<string>;
59
+
60
+ declare const ROLES_KEY = "roles";
61
+ declare const Roles: (...roles: string[]) => _nestjs_common.CustomDecorator<string>;
62
+
63
+ interface SecurityContext {
64
+ ipAddress: string;
65
+ userAgent: string;
66
+ }
67
+ /**
68
+ * Extract IP + User-Agent from the request as SecurityContext.
69
+ * Usage: @SecurityCtx() ctx: SecurityContext
70
+ */
71
+ declare const SecurityCtx: (...dataOrPipes: unknown[]) => ParameterDecorator;
72
+
73
+ /**
74
+ * Interceptor that ALWAYS responds with MessagePack.
75
+ * Designed for internal/service-to-service endpoints where
76
+ * both sides speak MessagePack via ServiceClient.
77
+ */
78
+ declare class InternalMsgPackInterceptor implements NestInterceptor {
79
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
80
+ }
81
+ /**
82
+ * Decorator shorthand — applies InternalMsgPackInterceptor.
83
+ *
84
+ * Usage:
85
+ * @Controller('internal')
86
+ * @UseMsgPack()
87
+ * @UseGuards(ServiceAuthGuard)
88
+ * export class InternalController { ... }
89
+ */
90
+ declare function UseMsgPack(): ClassDecorator & MethodDecorator;
91
+
92
+ declare const SERVICE_CLIENT = "SERVICE_CLIENT";
93
+ interface ServiceClientModuleOptions {
94
+ /** Override the service token (default: reads SERVICE_TO_SERVICE_TOKEN from env) */
95
+ serviceToken?: string;
96
+ /** Default timeout in ms (default: 10000) */
97
+ timeout?: number;
98
+ }
99
+ declare class ServiceClientModule {
100
+ /**
101
+ * Register with default config (reads SERVICE_TO_SERVICE_TOKEN from env).
102
+ */
103
+ static register(options?: ServiceClientModuleOptions): DynamicModule;
104
+ }
105
+
106
+ export { AuthGuard, CurrentUser, CurrentUserId, IS_PUBLIC_KEY, InternalMsgPackInterceptor, Public, ROLES_KEY, Roles, RolesGuard, SERVICE_CLIENT, type SecurityContext, SecurityCtx, ServiceAuthGuard, ServiceClientModule, type ServiceClientModuleOptions, UseMsgPack };