@coreiam/auth 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.
@@ -0,0 +1,21 @@
1
+ export interface CoreIAMConfig {
2
+ baseUrl?: string;
3
+ apiKey?: string;
4
+ tenantId?: string;
5
+ }
6
+ export declare class CoreIAM {
7
+ private apiKey;
8
+ private baseUrl;
9
+ constructor(config?: {
10
+ apiKey?: string;
11
+ baseUrl?: string;
12
+ });
13
+ refreshTokens(headers: Headers): Promise<string[] | null>;
14
+ proxy(path: string, init: RequestInit, incomingHeaders: Headers): Promise<Response>;
15
+ getCsrfToken(headers: Headers): Promise<Response>;
16
+ login(body: any, headers: Headers): Promise<Response>;
17
+ register(body: any, headers: Headers): Promise<Response>;
18
+ logout(headers: Headers): Promise<Response>;
19
+ loginWithCsrfProtection(identifier: string, password: string, incomingHeaders: Headers): Promise<Response>;
20
+ registerWithCsrfProtection(userData: any, incomingHeaders: Headers): Promise<Response>;
21
+ }
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ // src/core/index.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.CoreIAM = void 0;
5
+ const DEFAULT_GATEWAY_URL = "https://api.e-qalam.com";
6
+ class CoreIAM {
7
+ apiKey = '';
8
+ baseUrl = '';
9
+ constructor(config) {
10
+ this.apiKey = config?.apiKey || process.env.COREIAM_API_KEY || '';
11
+ this.baseUrl = config?.baseUrl || DEFAULT_GATEWAY_URL;
12
+ if (!this.apiKey)
13
+ throw new Error('CoreIAM: Missing API Key');
14
+ }
15
+ // Exposed publicly so the Next.js layer can use it for getSession()
16
+ async refreshTokens(headers) {
17
+ try {
18
+ const response = await fetch(`${this.baseUrl}/auth/refresh-token`, {
19
+ method: 'POST',
20
+ headers: {
21
+ 'X-API-Key': this.apiKey,
22
+ 'cookie': headers.get('cookie') || '',
23
+ },
24
+ });
25
+ if (response.ok) {
26
+ const setCookies = response.headers.getSetCookie();
27
+ if (setCookies && setCookies.length > 0) {
28
+ return setCookies;
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+ catch (error) {
34
+ return null;
35
+ }
36
+ }
37
+ async proxy(path, init, incomingHeaders) {
38
+ const url = `${this.baseUrl}${path}`;
39
+ const headers = new Headers(init.headers);
40
+ if (this.apiKey) {
41
+ headers.set('X-API-Key', this.apiKey);
42
+ }
43
+ const userAgent = incomingHeaders.get('user-agent');
44
+ if (userAgent)
45
+ headers.set('user-agent', userAgent);
46
+ const forwardedFor = incomingHeaders.get('x-forwarded-for');
47
+ if (forwardedFor)
48
+ headers.set('x-forwarded-for', forwardedFor);
49
+ const cookie = incomingHeaders.get('cookie');
50
+ if (cookie) {
51
+ headers.set('cookie', cookie);
52
+ const jwtMatch = cookie.match(/jwt=([^;]+)/);
53
+ if (jwtMatch) {
54
+ headers.set('Authorization', `Bearer ${jwtMatch[1]}`);
55
+ }
56
+ }
57
+ const csrf = incomingHeaders.get('x-csrf-token');
58
+ if (csrf)
59
+ headers.set('x-csrf-token', csrf);
60
+ // Removed the 401 interceptor here to prevent infinite loops.
61
+ // The getSession() action will handle refreshes safely.
62
+ return fetch(url, { ...init, headers });
63
+ }
64
+ async getCsrfToken(headers) {
65
+ return this.proxy('/auth/csrf-token', { method: 'POST' }, headers);
66
+ }
67
+ async login(body, headers) {
68
+ return this.proxy('/auth/login', { method: 'POST', body: JSON.stringify(body) }, headers);
69
+ }
70
+ async register(body, headers) {
71
+ return this.proxy('/auth/register', { method: 'POST', body: JSON.stringify(body) }, headers);
72
+ }
73
+ async logout(headers) {
74
+ return this.proxy('/auth/logout', { method: 'POST' }, headers);
75
+ }
76
+ async loginWithCsrfProtection(identifier, password, incomingHeaders) {
77
+ const csrfResponse = await this.getCsrfToken(incomingHeaders);
78
+ const csrfData = await csrfResponse.json();
79
+ const csrfToken = csrfData.csrfToken || csrfData._csrf;
80
+ if (!csrfToken)
81
+ throw new Error('Failed to obtain CSRF token');
82
+ const authHeaders = new Headers(incomingHeaders);
83
+ authHeaders.set('x-csrf-token', csrfToken);
84
+ const csrfCookies = csrfResponse.headers.getSetCookie();
85
+ const existingCookieStr = incomingHeaders.get('cookie') || '';
86
+ const newCookiePairs = csrfCookies.map(c => c.split(';')[0]);
87
+ if (newCookiePairs.length > 0) {
88
+ const combinedCookies = [existingCookieStr, ...newCookiePairs].filter(Boolean).join('; ');
89
+ authHeaders.set('cookie', combinedCookies);
90
+ }
91
+ return this.proxy('/auth/login', {
92
+ method: 'POST',
93
+ headers: { 'Content-Type': 'application/json' },
94
+ body: JSON.stringify({ identifier, password }),
95
+ }, authHeaders);
96
+ }
97
+ async registerWithCsrfProtection(userData, incomingHeaders) {
98
+ const csrfResponse = await this.getCsrfToken(incomingHeaders);
99
+ const csrfData = await csrfResponse.json();
100
+ const csrfToken = csrfData.csrfToken || csrfData._csrf;
101
+ if (!csrfToken)
102
+ throw new Error('Failed to obtain CSRF token');
103
+ const authHeaders = new Headers(incomingHeaders);
104
+ authHeaders.set('x-csrf-token', csrfToken);
105
+ const setCookieHeader = csrfResponse.headers.get('set-cookie');
106
+ if (setCookieHeader) {
107
+ const existingCookie = incomingHeaders.get('cookie') || '';
108
+ const updatedCookie = setCookieHeader.split(',').map(cookie => cookie.trim())
109
+ .map(cookie => cookie.split(';')[0])
110
+ .join('; ');
111
+ const combinedCookies = [existingCookie, updatedCookie].filter(Boolean).join('; ');
112
+ authHeaders.set('cookie', combinedCookies);
113
+ }
114
+ return this.proxy('/auth/register', {
115
+ method: 'POST',
116
+ headers: { 'Content-Type': 'application/json' },
117
+ body: JSON.stringify(userData),
118
+ }, authHeaders);
119
+ }
120
+ }
121
+ exports.CoreIAM = CoreIAM;
@@ -0,0 +1,13 @@
1
+ export interface AuthResult {
2
+ ok: boolean;
3
+ status: number;
4
+ data: any;
5
+ }
6
+ /**
7
+ * NEW: Securely gets the user session.
8
+ * Handles 401 by attempting a silent refresh internally.
9
+ */
10
+ export declare function getSession(): Promise<any | null>;
11
+ export declare function loginActionSdk(identifier: string, password: string): Promise<AuthResult>;
12
+ export declare function registerActionSdk(userData: any): Promise<AuthResult>;
13
+ export declare function logoutActionSdk(): Promise<AuthResult>;
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ // src/nextjs/actions.ts
3
+ 'use server';
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.getSession = getSession;
6
+ exports.loginActionSdk = loginActionSdk;
7
+ exports.registerActionSdk = registerActionSdk;
8
+ exports.logoutActionSdk = logoutActionSdk;
9
+ const headers_1 = require("next/headers");
10
+ const core_1 = require("../core");
11
+ const iam = new core_1.CoreIAM();
12
+ async function syncCookiesToNext(response) {
13
+ const cookieStore = await (0, headers_1.cookies)();
14
+ const setCookies = response.headers.getSetCookie();
15
+ for (const cookieStr of setCookies) {
16
+ const parts = cookieStr.split(';');
17
+ const [nameValue, ...attributes] = parts;
18
+ const [name, ...valueParts] = nameValue.split('=');
19
+ const cookieOptions = {};
20
+ let isDeleting = false;
21
+ attributes.forEach(attr => {
22
+ const [attrName, attrValue] = attr.trim().split('=');
23
+ const lowerName = attrName?.toLowerCase();
24
+ if (lowerName === 'httponly')
25
+ cookieOptions.httpOnly = true;
26
+ if (lowerName === 'secure')
27
+ cookieOptions.secure = true;
28
+ if (lowerName === 'path')
29
+ cookieOptions.path = attrValue || '/';
30
+ if (lowerName === 'max-age') {
31
+ const maxAge = parseInt(attrValue, 10);
32
+ if (maxAge <= 0) {
33
+ isDeleting = true;
34
+ }
35
+ else {
36
+ cookieOptions.maxAge = maxAge;
37
+ }
38
+ }
39
+ if (lowerName === 'expires') {
40
+ const expiryDate = new Date(attrValue);
41
+ if (expiryDate.getTime() < Date.now()) {
42
+ isDeleting = true;
43
+ }
44
+ else {
45
+ cookieOptions.expires = expiryDate;
46
+ }
47
+ }
48
+ if (lowerName === 'samesite')
49
+ cookieOptions.sameSite = attrValue?.toLowerCase() || 'lax';
50
+ });
51
+ const cookieName = name.trim();
52
+ const cookieValue = valueParts.join('=').trim();
53
+ if (isDeleting || cookieValue === '') {
54
+ cookieStore.delete(cookieName);
55
+ }
56
+ else {
57
+ cookieStore.set(cookieName, cookieValue, cookieOptions);
58
+ }
59
+ }
60
+ }
61
+ /**
62
+ * NEW: Securely gets the user session.
63
+ * Handles 401 by attempting a silent refresh internally.
64
+ */
65
+ async function getSession() {
66
+ const reqHeaders = await (0, headers_1.headers)();
67
+ let response = await iam.proxy('/auth/profile', { method: 'GET' }, reqHeaders);
68
+ if (response.status === 401) {
69
+ const refreshResponse = await iam.refreshTokens(reqHeaders);
70
+ if (refreshResponse && refreshResponse.length > 0) {
71
+ // Sync the new cookies to the browser
72
+ const fakeRes = new Response(null, { headers: { 'set-cookie': refreshResponse.join(', ') } });
73
+ await syncCookiesToNext(fakeRes);
74
+ // Build new headers with the refreshed cookies and retry
75
+ const cookieStore = await (0, headers_1.cookies)();
76
+ const newJwt = cookieStore.get('jwt')?.value;
77
+ const newRefresh = cookieStore.get('refreshToken')?.value;
78
+ const retryHeaders = new Headers(reqHeaders);
79
+ if (newJwt && newRefresh) {
80
+ retryHeaders.set('cookie', `jwt=${newJwt}; refreshToken=${newRefresh}`);
81
+ retryHeaders.set('Authorization', `Bearer ${newJwt}`);
82
+ }
83
+ response = await iam.proxy('/auth/profile', { method: 'GET' }, retryHeaders);
84
+ }
85
+ }
86
+ if (!response.ok)
87
+ return null;
88
+ return response.json();
89
+ }
90
+ async function loginActionSdk(identifier, password) {
91
+ const reqHeaders = await (0, headers_1.headers)();
92
+ try {
93
+ const response = await iam.loginWithCsrfProtection(identifier, password, reqHeaders);
94
+ await syncCookiesToNext(response);
95
+ return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
96
+ }
97
+ catch (error) {
98
+ return { ok: false, status: 500, data: { error: error instanceof Error ? error.message : 'Login failed' } };
99
+ }
100
+ }
101
+ async function registerActionSdk(userData) {
102
+ const reqHeaders = await (0, headers_1.headers)();
103
+ try {
104
+ const response = await iam.registerWithCsrfProtection(userData, reqHeaders);
105
+ await syncCookiesToNext(response);
106
+ return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
107
+ }
108
+ catch (error) {
109
+ return { ok: false, status: 500, data: { error: error instanceof Error ? error.message : 'Registration failed' } };
110
+ }
111
+ }
112
+ async function logoutActionSdk() {
113
+ const reqHeaders = await (0, headers_1.headers)();
114
+ try {
115
+ const response = await iam.logout(reqHeaders);
116
+ await syncCookiesToNext(response);
117
+ return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
118
+ }
119
+ catch (error) {
120
+ return { ok: false, status: 500, data: { error: 'Logout failed' } };
121
+ }
122
+ }
@@ -0,0 +1,9 @@
1
+ export { CoreIAMProvider, useAuth } from './provider';
2
+ export { loginActionSdk, registerActionSdk, logoutActionSdk, getSession } from './actions';
3
+ import { NextRequest, NextResponse } from 'next/server';
4
+ import { CoreIAM } from '../core';
5
+ export declare class NextHandlers {
6
+ private iam;
7
+ constructor(iam: CoreIAM);
8
+ handleProxy(req: NextRequest, internalPath: string): Promise<NextResponse<any>>;
9
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NextHandlers = exports.getSession = exports.logoutActionSdk = exports.registerActionSdk = exports.loginActionSdk = exports.useAuth = exports.CoreIAMProvider = void 0;
4
+ // src/nextjs/index.ts
5
+ var provider_1 = require("./provider");
6
+ Object.defineProperty(exports, "CoreIAMProvider", { enumerable: true, get: function () { return provider_1.CoreIAMProvider; } });
7
+ Object.defineProperty(exports, "useAuth", { enumerable: true, get: function () { return provider_1.useAuth; } });
8
+ var actions_1 = require("./actions");
9
+ Object.defineProperty(exports, "loginActionSdk", { enumerable: true, get: function () { return actions_1.loginActionSdk; } });
10
+ Object.defineProperty(exports, "registerActionSdk", { enumerable: true, get: function () { return actions_1.registerActionSdk; } });
11
+ Object.defineProperty(exports, "logoutActionSdk", { enumerable: true, get: function () { return actions_1.logoutActionSdk; } });
12
+ Object.defineProperty(exports, "getSession", { enumerable: true, get: function () { return actions_1.getSession; } });
13
+ const server_1 = require("next/server");
14
+ class NextHandlers {
15
+ iam;
16
+ constructor(iam) {
17
+ this.iam = iam;
18
+ }
19
+ async handleProxy(req, internalPath) {
20
+ const method = internalPath === '/csrf' ? 'POST' : req.method;
21
+ const body = method !== 'GET' ? await req.json().catch(() => undefined) : undefined;
22
+ let targetPath = internalPath;
23
+ if (internalPath === '/csrf') {
24
+ targetPath = '/auth/csrf-token';
25
+ }
26
+ else if (!internalPath.startsWith('/auth')) {
27
+ targetPath = `/auth${internalPath}`;
28
+ }
29
+ const response = await this.iam.proxy(targetPath, {
30
+ method: method,
31
+ body: body ? JSON.stringify(body) : undefined,
32
+ headers: { 'Content-Type': 'application/json' },
33
+ }, req.headers);
34
+ const data = await response.json().catch(() => ({}));
35
+ const res = server_1.NextResponse.json(data, { status: response.status });
36
+ const setCookies = response.headers.getSetCookie();
37
+ if (setCookies && setCookies.length > 0) {
38
+ setCookies.forEach(cookie => {
39
+ res.headers.append('set-cookie', cookie);
40
+ });
41
+ }
42
+ return res;
43
+ }
44
+ }
45
+ exports.NextHandlers = NextHandlers;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ interface AuthContextType {
3
+ user: any | null;
4
+ isLoading: boolean;
5
+ }
6
+ export declare function CoreIAMProvider({ children }: {
7
+ children: React.ReactNode;
8
+ }): import("react/jsx-runtime").JSX.Element;
9
+ export declare function useAuth(): AuthContextType;
10
+ export {};
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ // src/nextjs/provider.tsx
3
+ 'use client';
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.CoreIAMProvider = CoreIAMProvider;
6
+ exports.useAuth = useAuth;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const react_1 = require("react");
9
+ const AuthContext = (0, react_1.createContext)({
10
+ user: null,
11
+ isLoading: true,
12
+ });
13
+ function CoreIAMProvider({ children }) {
14
+ const [user, setUser] = (0, react_1.useState)(null);
15
+ const [isLoading, setIsLoading] = (0, react_1.useState)(true);
16
+ (0, react_1.useEffect)(() => {
17
+ fetch('/api/auth/session')
18
+ .then(res => res.ok ? res.json() : null)
19
+ .then(data => setUser(data))
20
+ .finally(() => setIsLoading(false));
21
+ }, []);
22
+ return ((0, jsx_runtime_1.jsx)(AuthContext.Provider, { value: { user, isLoading }, children: children }));
23
+ }
24
+ function useAuth() {
25
+ return (0, react_1.useContext)(AuthContext);
26
+ }
@@ -0,0 +1,2 @@
1
+ export { CoreIAMProvider, useAuth } from './provider';
2
+ export { CoreIAM } from '../core';
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CoreIAM = exports.useAuth = exports.CoreIAMProvider = void 0;
4
+ // src/react/index.ts
5
+ var provider_1 = require("./provider");
6
+ Object.defineProperty(exports, "CoreIAMProvider", { enumerable: true, get: function () { return provider_1.CoreIAMProvider; } });
7
+ Object.defineProperty(exports, "useAuth", { enumerable: true, get: function () { return provider_1.useAuth; } });
8
+ var core_1 = require("../core");
9
+ Object.defineProperty(exports, "CoreIAM", { enumerable: true, get: function () { return core_1.CoreIAM; } });
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ interface AuthContextType {
3
+ user: any | null;
4
+ isLoading: boolean;
5
+ }
6
+ export declare function CoreIAMProvider({ children, sessionUrl }: {
7
+ children: React.ReactNode;
8
+ sessionUrl?: string;
9
+ }): import("react/jsx-runtime").JSX.Element;
10
+ export declare function useAuth(): AuthContextType;
11
+ export {};
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ // src/react/provider.tsx
3
+ 'use client';
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.CoreIAMProvider = CoreIAMProvider;
6
+ exports.useAuth = useAuth;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const react_1 = require("react");
9
+ const AuthContext = (0, react_1.createContext)({
10
+ user: null,
11
+ isLoading: true,
12
+ });
13
+ function CoreIAMProvider({ children, sessionUrl = '/api/auth/session' }) {
14
+ const [user, setUser] = (0, react_1.useState)(null);
15
+ const [isLoading, setIsLoading] = (0, react_1.useState)(true);
16
+ (0, react_1.useEffect)(() => {
17
+ fetch(sessionUrl, { credentials: 'include' })
18
+ .then(res => res.ok ? res.json() : null)
19
+ .then(data => setUser(data))
20
+ .finally(() => setIsLoading(false));
21
+ }, [sessionUrl]);
22
+ return ((0, jsx_runtime_1.jsx)(AuthContext.Provider, { value: { user, isLoading }, children: children }));
23
+ }
24
+ function useAuth() {
25
+ return (0, react_1.useContext)(AuthContext);
26
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@coreiam/auth",
3
+ "version": "1.0.0",
4
+ "main": "./dist/core/index.js",
5
+ "types": "./dist/core/index.d.ts",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/core/index.d.ts",
9
+ "import": "./dist/core/index.js",
10
+ "require": "./dist/core/index.js"
11
+ },
12
+ "./nextjs": {
13
+ "types": "./dist/nextjs/index.d.ts",
14
+ "import": "./dist/nextjs/index.js",
15
+ "require": "./dist/nextjs/index.js"
16
+ },
17
+ "./react": {
18
+ "types": "./dist/react/index.d.ts",
19
+ "import": "./dist/react/index.js",
20
+ "require": "./dist/react/index.js"
21
+ }
22
+ },
23
+ "files": ["dist"],
24
+ "scripts": {
25
+ "build": "tsc"
26
+ },
27
+ "keywords": ["coreiam", "auth", "saas", "multi-tenant", "nextjs"],
28
+ "author": "coreIAM",
29
+ "license": "MIT",
30
+ "publishConfig": { "access": "public" },
31
+ "peerDependencies": {
32
+ "next": ">=14",
33
+ "react": ">=18"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "next": { "optional": true },
37
+ "react": { "optional": true }
38
+ },
39
+ "devDependencies": {
40
+ "@types/react": "^19.2.7",
41
+ "next": "^16.2.11",
42
+ "react": "^19.2.7",
43
+ "typescript": "^5.0.0"
44
+ }
45
+ }