@lanonasis/oauth-client 1.2.8 → 2.0.3
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/README.md +183 -15
- package/dist/{api-key-storage-web-J3W8nQi2.d.cts → api-key-storage-web-DUyiN9mC.d.cts} +11 -5
- package/dist/{api-key-storage-web-J3W8nQi2.d.ts → api-key-storage-web-DUyiN9mC.d.ts} +11 -5
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.ts +2 -2
- package/dist/constants-BZPTHasL.d.cts +110 -0
- package/dist/constants-BZPTHasL.d.ts +110 -0
- package/dist/index.cjs +79 -40
- package/dist/index.d.cts +15 -3
- package/dist/index.d.ts +15 -3
- package/dist/index.mjs +79 -40
- package/dist/react/index.cjs +261 -0
- package/dist/react/index.d.cts +95 -0
- package/dist/react/index.d.ts +95 -0
- package/dist/react/index.mjs +238 -0
- package/dist/server/index.cjs +169 -0
- package/dist/server/index.d.cts +184 -0
- package/dist/server/index.d.ts +184 -0
- package/dist/server/index.mjs +146 -0
- package/package.json +57 -15
|
@@ -0,0 +1,169 @@
|
|
|
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/server/index.ts
|
|
21
|
+
var server_exports = {};
|
|
22
|
+
__export(server_exports, {
|
|
23
|
+
COOKIE_NAMES: () => COOKIE_NAMES,
|
|
24
|
+
DEFAULT_AUTH_GATEWAY: () => DEFAULT_AUTH_GATEWAY,
|
|
25
|
+
DEFAULT_COOKIE_DOMAIN: () => DEFAULT_COOKIE_DOMAIN,
|
|
26
|
+
DEFAULT_PROJECT_SCOPE: () => DEFAULT_PROJECT_SCOPE,
|
|
27
|
+
getSSOUserFromRequest: () => getSSOUserFromRequest,
|
|
28
|
+
getSessionToken: () => getSessionToken,
|
|
29
|
+
getSessionTokenFromRequest: () => getSessionTokenFromRequest,
|
|
30
|
+
hasAuthCookiesServer: () => hasAuthCookiesServer,
|
|
31
|
+
hasSSOfromRequest: () => hasSSOfromRequest,
|
|
32
|
+
hasSessionCookieServer: () => hasSessionCookieServer,
|
|
33
|
+
optionalAuth: () => optionalAuth,
|
|
34
|
+
parseCookieHeader: () => parseCookieHeader,
|
|
35
|
+
parseUserCookieServer: () => parseUserCookieServer,
|
|
36
|
+
requireAuth: () => requireAuth,
|
|
37
|
+
requireRole: () => requireRole,
|
|
38
|
+
validateSessionMiddleware: () => validateSessionMiddleware
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(server_exports);
|
|
41
|
+
|
|
42
|
+
// src/cookies/constants.ts
|
|
43
|
+
var COOKIE_NAMES = {
|
|
44
|
+
/** HttpOnly JWT session token */
|
|
45
|
+
SESSION: "lanonasis_session",
|
|
46
|
+
/** Readable user metadata (JSON) */
|
|
47
|
+
USER: "lanonasis_user"
|
|
48
|
+
};
|
|
49
|
+
var DEFAULT_COOKIE_DOMAIN = ".lanonasis.com";
|
|
50
|
+
var DEFAULT_AUTH_GATEWAY = "https://auth.lanonasis.com";
|
|
51
|
+
var DEFAULT_PROJECT_SCOPE = "lanonasis-maas";
|
|
52
|
+
|
|
53
|
+
// src/server/cookie-utils.ts
|
|
54
|
+
function parseCookieHeader(cookieHeader) {
|
|
55
|
+
if (!cookieHeader) return {};
|
|
56
|
+
const cookies = {};
|
|
57
|
+
const pairs = cookieHeader.split(";");
|
|
58
|
+
for (const pair of pairs) {
|
|
59
|
+
const [name, ...valueParts] = pair.trim().split("=");
|
|
60
|
+
if (name) {
|
|
61
|
+
cookies[name.trim()] = valueParts.join("=").trim();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return cookies;
|
|
65
|
+
}
|
|
66
|
+
function getSessionToken(cookies) {
|
|
67
|
+
if (!cookies) return null;
|
|
68
|
+
const parsed = typeof cookies === "string" ? parseCookieHeader(cookies) : cookies;
|
|
69
|
+
const token = parsed[COOKIE_NAMES.SESSION];
|
|
70
|
+
return token || null;
|
|
71
|
+
}
|
|
72
|
+
function parseUserCookieServer(cookies) {
|
|
73
|
+
if (!cookies) return null;
|
|
74
|
+
try {
|
|
75
|
+
const parsed = typeof cookies === "string" ? parseCookieHeader(cookies) : cookies;
|
|
76
|
+
const userCookie = parsed[COOKIE_NAMES.USER];
|
|
77
|
+
if (!userCookie) return null;
|
|
78
|
+
const decoded = decodeURIComponent(userCookie);
|
|
79
|
+
const user = JSON.parse(decoded);
|
|
80
|
+
if (!user.id || !user.email || !user.role) {
|
|
81
|
+
console.warn("[oauth-client/server] Invalid user cookie: missing required fields");
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return user;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.warn("[oauth-client/server] Failed to parse user cookie:", error);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function hasSessionCookieServer(cookies) {
|
|
91
|
+
if (!cookies) return false;
|
|
92
|
+
const parsed = typeof cookies === "string" ? parseCookieHeader(cookies) : cookies;
|
|
93
|
+
return COOKIE_NAMES.SESSION in parsed && !!parsed[COOKIE_NAMES.SESSION];
|
|
94
|
+
}
|
|
95
|
+
function hasAuthCookiesServer(cookies) {
|
|
96
|
+
return hasSessionCookieServer(cookies) && parseUserCookieServer(cookies) !== null;
|
|
97
|
+
}
|
|
98
|
+
function getSSOUserFromRequest(req) {
|
|
99
|
+
if (req.cookies) {
|
|
100
|
+
return parseUserCookieServer(req.cookies);
|
|
101
|
+
}
|
|
102
|
+
return parseUserCookieServer(req.headers?.cookie);
|
|
103
|
+
}
|
|
104
|
+
function getSessionTokenFromRequest(req) {
|
|
105
|
+
if (req.cookies) {
|
|
106
|
+
return getSessionToken(req.cookies);
|
|
107
|
+
}
|
|
108
|
+
return getSessionToken(req.headers?.cookie);
|
|
109
|
+
}
|
|
110
|
+
function hasSSOfromRequest(req) {
|
|
111
|
+
if (req.cookies) {
|
|
112
|
+
return hasAuthCookiesServer(req.cookies);
|
|
113
|
+
}
|
|
114
|
+
return hasAuthCookiesServer(req.headers?.cookie);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/server/middleware.ts
|
|
118
|
+
function validateSessionMiddleware(config = {}) {
|
|
119
|
+
const {
|
|
120
|
+
cookieDomain = DEFAULT_COOKIE_DOMAIN,
|
|
121
|
+
allowAnonymous = false
|
|
122
|
+
} = config;
|
|
123
|
+
return (req, res, next) => {
|
|
124
|
+
if (hasSSOfromRequest(req)) {
|
|
125
|
+
const user = getSSOUserFromRequest(req);
|
|
126
|
+
if (user) {
|
|
127
|
+
req.user = user;
|
|
128
|
+
return next();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (allowAnonymous) {
|
|
132
|
+
return next();
|
|
133
|
+
}
|
|
134
|
+
res.clearCookie(COOKIE_NAMES.SESSION, { domain: cookieDomain, path: "/" });
|
|
135
|
+
res.clearCookie(COOKIE_NAMES.USER, { domain: cookieDomain, path: "/" });
|
|
136
|
+
return res.status(401).json({
|
|
137
|
+
error: "Authentication required",
|
|
138
|
+
code: "AUTH_REQUIRED",
|
|
139
|
+
login_url: `${config.authGatewayUrl || DEFAULT_AUTH_GATEWAY}/web/login`
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function requireAuth(config = {}) {
|
|
144
|
+
return validateSessionMiddleware({ ...config, allowAnonymous: false });
|
|
145
|
+
}
|
|
146
|
+
function optionalAuth(config = {}) {
|
|
147
|
+
return validateSessionMiddleware({ ...config, allowAnonymous: true });
|
|
148
|
+
}
|
|
149
|
+
function requireRole(role, config = {}) {
|
|
150
|
+
const roles = Array.isArray(role) ? role : [role];
|
|
151
|
+
return (req, res, next) => {
|
|
152
|
+
if (!req.user) {
|
|
153
|
+
return res.status(401).json({
|
|
154
|
+
error: "Authentication required",
|
|
155
|
+
code: "AUTH_REQUIRED",
|
|
156
|
+
login_url: `${config.authGatewayUrl || DEFAULT_AUTH_GATEWAY}/web/login`
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (!roles.includes(req.user.role)) {
|
|
160
|
+
return res.status(403).json({
|
|
161
|
+
error: "Insufficient permissions",
|
|
162
|
+
code: "FORBIDDEN",
|
|
163
|
+
required_role: roles,
|
|
164
|
+
current_role: req.user.role
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return next();
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { d as SSOUser } from '../constants-BZPTHasL.cjs';
|
|
2
|
+
export { C as COOKIE_NAMES, D as DEFAULT_AUTH_GATEWAY, e as DEFAULT_COOKIE_DOMAIN, g as DEFAULT_PROJECT_SCOPE } from '../constants-BZPTHasL.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Server-side types for SSO authentication
|
|
6
|
+
* @module @lanonasis/oauth-client/server
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Express-like request interface for middleware compatibility
|
|
11
|
+
*/
|
|
12
|
+
interface ServerRequest {
|
|
13
|
+
cookies?: Record<string, string>;
|
|
14
|
+
headers?: {
|
|
15
|
+
cookie?: string;
|
|
16
|
+
authorization?: string;
|
|
17
|
+
'x-api-key'?: string;
|
|
18
|
+
[key: string]: string | string[] | undefined;
|
|
19
|
+
};
|
|
20
|
+
user?: SSOUser;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Express-like response interface
|
|
24
|
+
*/
|
|
25
|
+
interface ServerResponse {
|
|
26
|
+
status: (code: number) => ServerResponse;
|
|
27
|
+
json: (data: unknown) => void;
|
|
28
|
+
clearCookie: (name: string, options?: CookieOptions) => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Express-like next function
|
|
32
|
+
*/
|
|
33
|
+
type NextFunction = (error?: Error) => void;
|
|
34
|
+
/**
|
|
35
|
+
* Cookie options for clearCookie
|
|
36
|
+
*/
|
|
37
|
+
interface CookieOptions {
|
|
38
|
+
domain?: string;
|
|
39
|
+
path?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Middleware configuration
|
|
43
|
+
*/
|
|
44
|
+
interface MiddlewareConfig {
|
|
45
|
+
/** Auth gateway URL for token validation */
|
|
46
|
+
authGatewayUrl?: string;
|
|
47
|
+
/** Project scope for multi-tenant auth */
|
|
48
|
+
projectScope?: string;
|
|
49
|
+
/** Cookie domain (default: .lanonasis.com) */
|
|
50
|
+
cookieDomain?: string;
|
|
51
|
+
/** Allow unauthenticated requests to pass through */
|
|
52
|
+
allowAnonymous?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validation result for auth checks
|
|
56
|
+
*/
|
|
57
|
+
interface AuthValidationResult {
|
|
58
|
+
valid: boolean;
|
|
59
|
+
user?: SSOUser;
|
|
60
|
+
error?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Server-side cookie utilities for SSO authentication
|
|
65
|
+
* Use these utilities in Express/Node.js server middleware
|
|
66
|
+
*
|
|
67
|
+
* @module @lanonasis/oauth-client/server
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Parse cookies from a cookie string (from request headers)
|
|
72
|
+
* @param cookieHeader - The Cookie header value (e.g., "name=value; name2=value2")
|
|
73
|
+
* @returns Object mapping cookie names to values
|
|
74
|
+
*/
|
|
75
|
+
declare function parseCookieHeader(cookieHeader: string | undefined): Record<string, string>;
|
|
76
|
+
/**
|
|
77
|
+
* Get the session token from cookies (request.cookies or cookie header)
|
|
78
|
+
* @param cookies - Parsed cookies object or cookie header string
|
|
79
|
+
* @returns The session token or null
|
|
80
|
+
*/
|
|
81
|
+
declare function getSessionToken(cookies: Record<string, string> | string | undefined): string | null;
|
|
82
|
+
/**
|
|
83
|
+
* Parse the user cookie from server-side request
|
|
84
|
+
* @param cookies - Parsed cookies object or cookie header string
|
|
85
|
+
* @returns User data or null if cookie doesn't exist or is invalid
|
|
86
|
+
*/
|
|
87
|
+
declare function parseUserCookieServer(cookies: Record<string, string> | string | undefined): SSOUser | null;
|
|
88
|
+
/**
|
|
89
|
+
* Check if session cookie exists in the cookies
|
|
90
|
+
* @param cookies - Parsed cookies object or cookie header string
|
|
91
|
+
* @returns true if lanonasis_session cookie exists
|
|
92
|
+
*/
|
|
93
|
+
declare function hasSessionCookieServer(cookies: Record<string, string> | string | undefined): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Check if user appears to be authenticated based on cookies (server-side)
|
|
96
|
+
* @param cookies - Parsed cookies object or cookie header string
|
|
97
|
+
* @returns true if both session and user cookies exist
|
|
98
|
+
*/
|
|
99
|
+
declare function hasAuthCookiesServer(cookies: Record<string, string> | string | undefined): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Get SSO user from Express request (checks req.cookies first, then cookie header)
|
|
102
|
+
* @param req - Express-like request object
|
|
103
|
+
* @returns User data or null
|
|
104
|
+
*/
|
|
105
|
+
declare function getSSOUserFromRequest(req: ServerRequest): SSOUser | null;
|
|
106
|
+
/**
|
|
107
|
+
* Get session token from Express request
|
|
108
|
+
* @param req - Express-like request object
|
|
109
|
+
* @returns Session token or null
|
|
110
|
+
*/
|
|
111
|
+
declare function getSessionTokenFromRequest(req: ServerRequest): string | null;
|
|
112
|
+
/**
|
|
113
|
+
* Check if request has SSO authentication
|
|
114
|
+
* @param req - Express-like request object
|
|
115
|
+
* @returns true if SSO cookies are present
|
|
116
|
+
*/
|
|
117
|
+
declare function hasSSOfromRequest(req: ServerRequest): boolean;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Express middleware for SSO authentication
|
|
121
|
+
* @module @lanonasis/oauth-client/server
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Create a middleware that validates SSO session cookies
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* import { validateSessionMiddleware } from '@lanonasis/oauth-client/server';
|
|
130
|
+
*
|
|
131
|
+
* const auth = validateSessionMiddleware({
|
|
132
|
+
* authGatewayUrl: process.env.AUTH_GATEWAY_URL,
|
|
133
|
+
* projectScope: 'my-app'
|
|
134
|
+
* });
|
|
135
|
+
*
|
|
136
|
+
* app.use('/api', auth);
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
declare function validateSessionMiddleware(config?: MiddlewareConfig): (req: ServerRequest, res: ServerResponse, next: NextFunction) => void;
|
|
140
|
+
/**
|
|
141
|
+
* Middleware that requires authentication (returns 401 if not authenticated)
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* import { requireAuth } from '@lanonasis/oauth-client/server';
|
|
146
|
+
*
|
|
147
|
+
* app.get('/api/profile', requireAuth(), (req, res) => {
|
|
148
|
+
* res.json({ user: req.user });
|
|
149
|
+
* });
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
declare function requireAuth(config?: MiddlewareConfig): (req: ServerRequest, res: ServerResponse, next: NextFunction) => void;
|
|
153
|
+
/**
|
|
154
|
+
* Middleware that allows anonymous access but attaches user if authenticated
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* import { optionalAuth } from '@lanonasis/oauth-client/server';
|
|
159
|
+
*
|
|
160
|
+
* app.get('/api/public', optionalAuth(), (req, res) => {
|
|
161
|
+
* if (req.user) {
|
|
162
|
+
* res.json({ message: `Hello ${req.user.email}` });
|
|
163
|
+
* } else {
|
|
164
|
+
* res.json({ message: 'Hello guest' });
|
|
165
|
+
* }
|
|
166
|
+
* });
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
declare function optionalAuth(config?: MiddlewareConfig): (req: ServerRequest, res: ServerResponse, next: NextFunction) => void;
|
|
170
|
+
/**
|
|
171
|
+
* Middleware that requires a specific role
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* import { requireRole } from '@lanonasis/oauth-client/server';
|
|
176
|
+
*
|
|
177
|
+
* app.delete('/api/users/:id', requireRole('admin'), (req, res) => {
|
|
178
|
+
* // Only admins can delete users
|
|
179
|
+
* });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
declare function requireRole(role: string | string[], config?: MiddlewareConfig): (req: ServerRequest, res: ServerResponse, next: NextFunction) => void;
|
|
183
|
+
|
|
184
|
+
export { type AuthValidationResult, type CookieOptions, type MiddlewareConfig, type NextFunction, SSOUser, type ServerRequest, type ServerResponse, getSSOUserFromRequest, getSessionToken, getSessionTokenFromRequest, hasAuthCookiesServer, hasSSOfromRequest, hasSessionCookieServer, optionalAuth, parseCookieHeader, parseUserCookieServer, requireAuth, requireRole, validateSessionMiddleware };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { d as SSOUser } from '../constants-BZPTHasL.js';
|
|
2
|
+
export { C as COOKIE_NAMES, D as DEFAULT_AUTH_GATEWAY, e as DEFAULT_COOKIE_DOMAIN, g as DEFAULT_PROJECT_SCOPE } from '../constants-BZPTHasL.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Server-side types for SSO authentication
|
|
6
|
+
* @module @lanonasis/oauth-client/server
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Express-like request interface for middleware compatibility
|
|
11
|
+
*/
|
|
12
|
+
interface ServerRequest {
|
|
13
|
+
cookies?: Record<string, string>;
|
|
14
|
+
headers?: {
|
|
15
|
+
cookie?: string;
|
|
16
|
+
authorization?: string;
|
|
17
|
+
'x-api-key'?: string;
|
|
18
|
+
[key: string]: string | string[] | undefined;
|
|
19
|
+
};
|
|
20
|
+
user?: SSOUser;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Express-like response interface
|
|
24
|
+
*/
|
|
25
|
+
interface ServerResponse {
|
|
26
|
+
status: (code: number) => ServerResponse;
|
|
27
|
+
json: (data: unknown) => void;
|
|
28
|
+
clearCookie: (name: string, options?: CookieOptions) => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Express-like next function
|
|
32
|
+
*/
|
|
33
|
+
type NextFunction = (error?: Error) => void;
|
|
34
|
+
/**
|
|
35
|
+
* Cookie options for clearCookie
|
|
36
|
+
*/
|
|
37
|
+
interface CookieOptions {
|
|
38
|
+
domain?: string;
|
|
39
|
+
path?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Middleware configuration
|
|
43
|
+
*/
|
|
44
|
+
interface MiddlewareConfig {
|
|
45
|
+
/** Auth gateway URL for token validation */
|
|
46
|
+
authGatewayUrl?: string;
|
|
47
|
+
/** Project scope for multi-tenant auth */
|
|
48
|
+
projectScope?: string;
|
|
49
|
+
/** Cookie domain (default: .lanonasis.com) */
|
|
50
|
+
cookieDomain?: string;
|
|
51
|
+
/** Allow unauthenticated requests to pass through */
|
|
52
|
+
allowAnonymous?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validation result for auth checks
|
|
56
|
+
*/
|
|
57
|
+
interface AuthValidationResult {
|
|
58
|
+
valid: boolean;
|
|
59
|
+
user?: SSOUser;
|
|
60
|
+
error?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Server-side cookie utilities for SSO authentication
|
|
65
|
+
* Use these utilities in Express/Node.js server middleware
|
|
66
|
+
*
|
|
67
|
+
* @module @lanonasis/oauth-client/server
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Parse cookies from a cookie string (from request headers)
|
|
72
|
+
* @param cookieHeader - The Cookie header value (e.g., "name=value; name2=value2")
|
|
73
|
+
* @returns Object mapping cookie names to values
|
|
74
|
+
*/
|
|
75
|
+
declare function parseCookieHeader(cookieHeader: string | undefined): Record<string, string>;
|
|
76
|
+
/**
|
|
77
|
+
* Get the session token from cookies (request.cookies or cookie header)
|
|
78
|
+
* @param cookies - Parsed cookies object or cookie header string
|
|
79
|
+
* @returns The session token or null
|
|
80
|
+
*/
|
|
81
|
+
declare function getSessionToken(cookies: Record<string, string> | string | undefined): string | null;
|
|
82
|
+
/**
|
|
83
|
+
* Parse the user cookie from server-side request
|
|
84
|
+
* @param cookies - Parsed cookies object or cookie header string
|
|
85
|
+
* @returns User data or null if cookie doesn't exist or is invalid
|
|
86
|
+
*/
|
|
87
|
+
declare function parseUserCookieServer(cookies: Record<string, string> | string | undefined): SSOUser | null;
|
|
88
|
+
/**
|
|
89
|
+
* Check if session cookie exists in the cookies
|
|
90
|
+
* @param cookies - Parsed cookies object or cookie header string
|
|
91
|
+
* @returns true if lanonasis_session cookie exists
|
|
92
|
+
*/
|
|
93
|
+
declare function hasSessionCookieServer(cookies: Record<string, string> | string | undefined): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Check if user appears to be authenticated based on cookies (server-side)
|
|
96
|
+
* @param cookies - Parsed cookies object or cookie header string
|
|
97
|
+
* @returns true if both session and user cookies exist
|
|
98
|
+
*/
|
|
99
|
+
declare function hasAuthCookiesServer(cookies: Record<string, string> | string | undefined): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Get SSO user from Express request (checks req.cookies first, then cookie header)
|
|
102
|
+
* @param req - Express-like request object
|
|
103
|
+
* @returns User data or null
|
|
104
|
+
*/
|
|
105
|
+
declare function getSSOUserFromRequest(req: ServerRequest): SSOUser | null;
|
|
106
|
+
/**
|
|
107
|
+
* Get session token from Express request
|
|
108
|
+
* @param req - Express-like request object
|
|
109
|
+
* @returns Session token or null
|
|
110
|
+
*/
|
|
111
|
+
declare function getSessionTokenFromRequest(req: ServerRequest): string | null;
|
|
112
|
+
/**
|
|
113
|
+
* Check if request has SSO authentication
|
|
114
|
+
* @param req - Express-like request object
|
|
115
|
+
* @returns true if SSO cookies are present
|
|
116
|
+
*/
|
|
117
|
+
declare function hasSSOfromRequest(req: ServerRequest): boolean;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Express middleware for SSO authentication
|
|
121
|
+
* @module @lanonasis/oauth-client/server
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Create a middleware that validates SSO session cookies
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* import { validateSessionMiddleware } from '@lanonasis/oauth-client/server';
|
|
130
|
+
*
|
|
131
|
+
* const auth = validateSessionMiddleware({
|
|
132
|
+
* authGatewayUrl: process.env.AUTH_GATEWAY_URL,
|
|
133
|
+
* projectScope: 'my-app'
|
|
134
|
+
* });
|
|
135
|
+
*
|
|
136
|
+
* app.use('/api', auth);
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
declare function validateSessionMiddleware(config?: MiddlewareConfig): (req: ServerRequest, res: ServerResponse, next: NextFunction) => void;
|
|
140
|
+
/**
|
|
141
|
+
* Middleware that requires authentication (returns 401 if not authenticated)
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* import { requireAuth } from '@lanonasis/oauth-client/server';
|
|
146
|
+
*
|
|
147
|
+
* app.get('/api/profile', requireAuth(), (req, res) => {
|
|
148
|
+
* res.json({ user: req.user });
|
|
149
|
+
* });
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
declare function requireAuth(config?: MiddlewareConfig): (req: ServerRequest, res: ServerResponse, next: NextFunction) => void;
|
|
153
|
+
/**
|
|
154
|
+
* Middleware that allows anonymous access but attaches user if authenticated
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* import { optionalAuth } from '@lanonasis/oauth-client/server';
|
|
159
|
+
*
|
|
160
|
+
* app.get('/api/public', optionalAuth(), (req, res) => {
|
|
161
|
+
* if (req.user) {
|
|
162
|
+
* res.json({ message: `Hello ${req.user.email}` });
|
|
163
|
+
* } else {
|
|
164
|
+
* res.json({ message: 'Hello guest' });
|
|
165
|
+
* }
|
|
166
|
+
* });
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
declare function optionalAuth(config?: MiddlewareConfig): (req: ServerRequest, res: ServerResponse, next: NextFunction) => void;
|
|
170
|
+
/**
|
|
171
|
+
* Middleware that requires a specific role
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* import { requireRole } from '@lanonasis/oauth-client/server';
|
|
176
|
+
*
|
|
177
|
+
* app.delete('/api/users/:id', requireRole('admin'), (req, res) => {
|
|
178
|
+
* // Only admins can delete users
|
|
179
|
+
* });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
declare function requireRole(role: string | string[], config?: MiddlewareConfig): (req: ServerRequest, res: ServerResponse, next: NextFunction) => void;
|
|
183
|
+
|
|
184
|
+
export { type AuthValidationResult, type CookieOptions, type MiddlewareConfig, type NextFunction, SSOUser, type ServerRequest, type ServerResponse, getSSOUserFromRequest, getSessionToken, getSessionTokenFromRequest, hasAuthCookiesServer, hasSSOfromRequest, hasSessionCookieServer, optionalAuth, parseCookieHeader, parseUserCookieServer, requireAuth, requireRole, validateSessionMiddleware };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// src/cookies/constants.ts
|
|
2
|
+
var COOKIE_NAMES = {
|
|
3
|
+
/** HttpOnly JWT session token */
|
|
4
|
+
SESSION: "lanonasis_session",
|
|
5
|
+
/** Readable user metadata (JSON) */
|
|
6
|
+
USER: "lanonasis_user"
|
|
7
|
+
};
|
|
8
|
+
var DEFAULT_COOKIE_DOMAIN = ".lanonasis.com";
|
|
9
|
+
var DEFAULT_AUTH_GATEWAY = "https://auth.lanonasis.com";
|
|
10
|
+
var DEFAULT_PROJECT_SCOPE = "lanonasis-maas";
|
|
11
|
+
|
|
12
|
+
// src/server/cookie-utils.ts
|
|
13
|
+
function parseCookieHeader(cookieHeader) {
|
|
14
|
+
if (!cookieHeader) return {};
|
|
15
|
+
const cookies = {};
|
|
16
|
+
const pairs = cookieHeader.split(";");
|
|
17
|
+
for (const pair of pairs) {
|
|
18
|
+
const [name, ...valueParts] = pair.trim().split("=");
|
|
19
|
+
if (name) {
|
|
20
|
+
cookies[name.trim()] = valueParts.join("=").trim();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return cookies;
|
|
24
|
+
}
|
|
25
|
+
function getSessionToken(cookies) {
|
|
26
|
+
if (!cookies) return null;
|
|
27
|
+
const parsed = typeof cookies === "string" ? parseCookieHeader(cookies) : cookies;
|
|
28
|
+
const token = parsed[COOKIE_NAMES.SESSION];
|
|
29
|
+
return token || null;
|
|
30
|
+
}
|
|
31
|
+
function parseUserCookieServer(cookies) {
|
|
32
|
+
if (!cookies) return null;
|
|
33
|
+
try {
|
|
34
|
+
const parsed = typeof cookies === "string" ? parseCookieHeader(cookies) : cookies;
|
|
35
|
+
const userCookie = parsed[COOKIE_NAMES.USER];
|
|
36
|
+
if (!userCookie) return null;
|
|
37
|
+
const decoded = decodeURIComponent(userCookie);
|
|
38
|
+
const user = JSON.parse(decoded);
|
|
39
|
+
if (!user.id || !user.email || !user.role) {
|
|
40
|
+
console.warn("[oauth-client/server] Invalid user cookie: missing required fields");
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return user;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.warn("[oauth-client/server] Failed to parse user cookie:", error);
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function hasSessionCookieServer(cookies) {
|
|
50
|
+
if (!cookies) return false;
|
|
51
|
+
const parsed = typeof cookies === "string" ? parseCookieHeader(cookies) : cookies;
|
|
52
|
+
return COOKIE_NAMES.SESSION in parsed && !!parsed[COOKIE_NAMES.SESSION];
|
|
53
|
+
}
|
|
54
|
+
function hasAuthCookiesServer(cookies) {
|
|
55
|
+
return hasSessionCookieServer(cookies) && parseUserCookieServer(cookies) !== null;
|
|
56
|
+
}
|
|
57
|
+
function getSSOUserFromRequest(req) {
|
|
58
|
+
if (req.cookies) {
|
|
59
|
+
return parseUserCookieServer(req.cookies);
|
|
60
|
+
}
|
|
61
|
+
return parseUserCookieServer(req.headers?.cookie);
|
|
62
|
+
}
|
|
63
|
+
function getSessionTokenFromRequest(req) {
|
|
64
|
+
if (req.cookies) {
|
|
65
|
+
return getSessionToken(req.cookies);
|
|
66
|
+
}
|
|
67
|
+
return getSessionToken(req.headers?.cookie);
|
|
68
|
+
}
|
|
69
|
+
function hasSSOfromRequest(req) {
|
|
70
|
+
if (req.cookies) {
|
|
71
|
+
return hasAuthCookiesServer(req.cookies);
|
|
72
|
+
}
|
|
73
|
+
return hasAuthCookiesServer(req.headers?.cookie);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/server/middleware.ts
|
|
77
|
+
function validateSessionMiddleware(config = {}) {
|
|
78
|
+
const {
|
|
79
|
+
cookieDomain = DEFAULT_COOKIE_DOMAIN,
|
|
80
|
+
allowAnonymous = false
|
|
81
|
+
} = config;
|
|
82
|
+
return (req, res, next) => {
|
|
83
|
+
if (hasSSOfromRequest(req)) {
|
|
84
|
+
const user = getSSOUserFromRequest(req);
|
|
85
|
+
if (user) {
|
|
86
|
+
req.user = user;
|
|
87
|
+
return next();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (allowAnonymous) {
|
|
91
|
+
return next();
|
|
92
|
+
}
|
|
93
|
+
res.clearCookie(COOKIE_NAMES.SESSION, { domain: cookieDomain, path: "/" });
|
|
94
|
+
res.clearCookie(COOKIE_NAMES.USER, { domain: cookieDomain, path: "/" });
|
|
95
|
+
return res.status(401).json({
|
|
96
|
+
error: "Authentication required",
|
|
97
|
+
code: "AUTH_REQUIRED",
|
|
98
|
+
login_url: `${config.authGatewayUrl || DEFAULT_AUTH_GATEWAY}/web/login`
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function requireAuth(config = {}) {
|
|
103
|
+
return validateSessionMiddleware({ ...config, allowAnonymous: false });
|
|
104
|
+
}
|
|
105
|
+
function optionalAuth(config = {}) {
|
|
106
|
+
return validateSessionMiddleware({ ...config, allowAnonymous: true });
|
|
107
|
+
}
|
|
108
|
+
function requireRole(role, config = {}) {
|
|
109
|
+
const roles = Array.isArray(role) ? role : [role];
|
|
110
|
+
return (req, res, next) => {
|
|
111
|
+
if (!req.user) {
|
|
112
|
+
return res.status(401).json({
|
|
113
|
+
error: "Authentication required",
|
|
114
|
+
code: "AUTH_REQUIRED",
|
|
115
|
+
login_url: `${config.authGatewayUrl || DEFAULT_AUTH_GATEWAY}/web/login`
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (!roles.includes(req.user.role)) {
|
|
119
|
+
return res.status(403).json({
|
|
120
|
+
error: "Insufficient permissions",
|
|
121
|
+
code: "FORBIDDEN",
|
|
122
|
+
required_role: roles,
|
|
123
|
+
current_role: req.user.role
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return next();
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export {
|
|
130
|
+
COOKIE_NAMES,
|
|
131
|
+
DEFAULT_AUTH_GATEWAY,
|
|
132
|
+
DEFAULT_COOKIE_DOMAIN,
|
|
133
|
+
DEFAULT_PROJECT_SCOPE,
|
|
134
|
+
getSSOUserFromRequest,
|
|
135
|
+
getSessionToken,
|
|
136
|
+
getSessionTokenFromRequest,
|
|
137
|
+
hasAuthCookiesServer,
|
|
138
|
+
hasSSOfromRequest,
|
|
139
|
+
hasSessionCookieServer,
|
|
140
|
+
optionalAuth,
|
|
141
|
+
parseCookieHeader,
|
|
142
|
+
parseUserCookieServer,
|
|
143
|
+
requireAuth,
|
|
144
|
+
requireRole,
|
|
145
|
+
validateSessionMiddleware
|
|
146
|
+
};
|