@zapkey/express 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # @zapkey/express
2
+
3
+ Express adapter for ZapKey.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @zapkey/core @zapkey/express express cookie-parser
9
+ ```
10
+
11
+ ## What it exports
12
+
13
+ - `requireAuth`
14
+ - `requireGuard`
15
+ - `setAuthCookie`
16
+ - `clearAuthCookie`
17
+ - `handleLogin`
18
+ - `handleRefresh`
19
+ - `handleLogout`
20
+ - `getUser`
21
+
22
+ ## Usage
23
+
24
+ ```ts
25
+ import cookieParser from 'cookie-parser';
26
+ import express from 'express';
27
+ import { initZapkey } from '@zapkey/core';
28
+ import { getUser, handleLogin, handleLogout, handleRefresh, requireAuth, requireGuard } from '@zapkey/express';
29
+ import { MemoryAuthorizationStore, MemoryTokenStore } from '@zapkey/stores-memory';
30
+
31
+ const authorization = new MemoryAuthorizationStore();
32
+ authorization.addRole('user_123', 'admin');
33
+ authorization.addRolePermission('admin', 'users:read');
34
+
35
+ const sdk = initZapkey({
36
+ mode: 'session',
37
+ accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
38
+ stores: {
39
+ token: new MemoryTokenStore(),
40
+ authorization,
41
+ },
42
+ });
43
+
44
+ const app = express();
45
+ app.use(express.json());
46
+ app.use(cookieParser());
47
+
48
+ app.post('/auth/login', async (req, res) => {
49
+ const user = await authenticateUser(req.body.email, req.body.password);
50
+ if (!user) {
51
+ return res.status(401).json({ error: 'Invalid credentials' });
52
+ }
53
+
54
+ await handleLogin(sdk, user.id, res, {
55
+ metadata: { role: user.role },
56
+ });
57
+ });
58
+
59
+ app.post('/auth/refresh', (req, res) => handleRefresh(sdk, req, res));
60
+ app.post('/auth/logout', (req, res) => handleLogout(sdk, req, res));
61
+
62
+ app.get('/me', requireAuth(sdk), (req, res) => {
63
+ res.json({ userId: getUser(req).userId });
64
+ });
65
+
66
+ app.get(
67
+ '/admin/users',
68
+ requireAuth(sdk),
69
+ requireGuard(sdk, { type: 'permission', permission: 'users:read' }),
70
+ (_req, res) => {
71
+ res.json({ ok: true });
72
+ },
73
+ );
74
+ ```
75
+
76
+ ## Notes
77
+
78
+ - Register `cookie-parser` before using the auth helpers.
79
+ - `requireAuth` checks the access-token cookie first and then falls back to the `Authorization` header.
80
+ - `handleLogout` clears the access cookie and revokes the refresh token with reason `'logout'` when refresh mode is enabled.
81
+ - Abuse protection is not automatic. Call `sdk.abuse.check(...)` from your routes when needed.
82
+
83
+ ## Full documentation
84
+
85
+ See [`../../docs/SDK_USAGE_GUIDE.md`](../../docs/SDK_USAGE_GUIDE.md) for full configuration, enterprise profile, OTP, abuse, and store guidance.
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var f=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var m=Object.getOwnPropertyNames;var g=Object.prototype.hasOwnProperty;var d=(e,t)=>{for(var o in t)f(e,o,{get:t[o],enumerable:!0})},O=(e,t,o,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of m(t))!g.call(e,n)&&n!==o&&f(e,n,{get:()=>t[n],enumerable:!(s=T(t,n))||s.enumerable});return e};var x=e=>O(f({},"__esModule",{value:!0}),e);var I={};d(I,{clearAuthCookie:()=>h,getUser:()=>N,handleLogin:()=>C,handleLogout:()=>E,handleRefresh:()=>A,requireAuth:()=>y,requireGuard:()=>l,setAuthCookie:()=>u});module.exports=x(I);var a=require("@zapkey/core");function y(e){return async(t,o,s)=>{try{let n=e.cookie.getAccessTokenName(),i=t.cookies?.[n];if(!i){let c=t.headers.authorization;c?.startsWith("Bearer ")&&(i=c.substring(7))}if(!i){o.status(401).json({error:"Unauthorized",code:"NO_TOKEN"});return}let r=await e.token.verifyAccessToken(i);t.user=r,s()}catch(n){k(n,o)}}}function l(e,t){return async(o,s,n)=>{try{if(!e.authorization){s.status(500).json({error:"Authorization not configured",code:"AUTHORIZATION_NOT_CONFIGURED",hint:"Provide AuthorizationStore in SDK config to use requireGuard"});return}let i=o.user;if(!i){s.status(401).json({error:"Unauthorized",code:"NO_USER"});return}await e.authorization.guard.enforce(i.userId,t),n()}catch(i){k(i,s)}}}function u(e,t,o,s){let n={httpOnly:s.httpOnly,secure:s.secure,sameSite:s.sameSite,maxAge:s.maxAge*1e3,path:s.path,domain:s.domain};e.cookie(t,o,n)}function h(e,t,o){let s={httpOnly:o.httpOnly,secure:o.secure,sameSite:o.sameSite,maxAge:0,path:o.path,domain:o.domain};e.clearCookie(t,s)}async function C(e,t,o,s){let n=s?.withRefresh??e.hasRefreshTokens(),i=s?.metadata!==void 0?{userId:t,metadata:s.metadata}:{userId:t};if(n){let r=await e.token.issue(i),c=e.cookie.getAccessTokenCookieOptions(r.expiresIn),p=e.getConfig().token.refreshTokenExpiresIn,R=e.cookie.getRefreshTokenCookieOptions(p);u(o,e.cookie.getAccessTokenName(),r.accessToken,c),u(o,e.cookie.getRefreshTokenName(),r.refreshToken,R),o.json({success:!0,accessToken:r.accessToken,expiresIn:r.expiresIn,refreshEnabled:!0})}else{let r=await e.token.issueAccessToken(i),c=e.cookie.getAccessTokenCookieOptions(r.expiresIn);u(o,e.cookie.getAccessTokenName(),r.accessToken,c),o.json({success:!0,accessToken:r.accessToken,expiresIn:r.expiresIn,refreshEnabled:!1})}}async function A(e,t,o){try{if(!e.hasRefreshTokens()||!e.token.rotate){o.status(400).json({error:"Refresh tokens are not enabled",code:"REFRESH_DISABLED",hint:"Set refreshTokens: true in SDK config to enable token refresh"});return}let s=t.cookies?.[e.cookie.getRefreshTokenName()];if(!s){o.status(401).json({error:"No refresh token",code:"NO_REFRESH_TOKEN"});return}let n=await e.token.rotate(s),i=e.cookie.getAccessTokenCookieOptions(n.expiresIn),r=e.getConfig().token.refreshTokenExpiresIn,c=e.cookie.getRefreshTokenCookieOptions(r);u(o,e.cookie.getAccessTokenName(),n.accessToken,i),u(o,e.cookie.getRefreshTokenName(),n.refreshToken,c),o.json({success:!0,accessToken:n.accessToken,expiresIn:n.expiresIn})}catch(s){k(s,o)}}async function E(e,t,o){try{let s=e.cookie.getClearCookieOptions();if(h(o,e.cookie.getAccessTokenName(),s),e.hasRefreshTokens()&&e.token.revokeToken){let n=t.cookies?.[e.cookie.getRefreshTokenName()];n&&await e.token.revokeToken(n,"logout"),h(o,e.cookie.getRefreshTokenName(),s)}o.json({success:!0})}catch(s){k(s,o)}}function k(e,t){e instanceof a.RateLimitError?t.status(e.statusCode).json({error:e.message,code:e.code,retryAfter:e.retryAfter}):e instanceof a.TokenError?t.status(e.statusCode).json({error:e.message,code:e.code}):e instanceof a.OTPError?t.status(e.statusCode).json({error:e.message,code:e.code}):e instanceof a.AuthorizationError?t.status(e.statusCode).json({error:e.message,code:e.code}):e instanceof a.AuthError?t.status(e.statusCode).json({error:e.message,code:e.code}):t.status(500).json({error:"Internal server error",code:"INTERNAL_ERROR"})}function N(e){return e.user}0&&(module.exports={clearAuthCookie,getUser,handleLogin,handleLogout,handleRefresh,requireAuth,requireGuard,setAuthCookie});
@@ -0,0 +1,76 @@
1
+ import { Response, Request, RequestHandler } from 'express';
2
+ import { CookieOptions, Zapkey, Guard } from '@zapkey/core';
3
+
4
+ /**
5
+ * Express Adapter
6
+ *
7
+ * CRITICAL: This is a THIN adapter. NO business logic.
8
+ * Only request/response mapping.
9
+ *
10
+ * Supports both token modes:
11
+ * - Access-Only: Only access token in cookies, no refresh endpoint
12
+ * - Access + Refresh: Full token lifecycle with rotation
13
+ */
14
+
15
+ /**
16
+ * Middleware to verify access token from cookie or Authorization header
17
+ *
18
+ * Works in BOTH modes (access-only and access+refresh)
19
+ */
20
+ declare function requireAuth(sdk: Zapkey): RequestHandler;
21
+ /**
22
+ * Middleware to enforce authorization guards
23
+ */
24
+ declare function requireGuard(sdk: Zapkey, guard: Guard): RequestHandler;
25
+ /**
26
+ * Cookie helper - set cookie with SDK options
27
+ */
28
+ declare function setAuthCookie(res: Response, name: string, value: string, options: CookieOptions): void;
29
+ /**
30
+ * Cookie helper - clear auth cookie
31
+ */
32
+ declare function clearAuthCookie(res: Response, name: string, options: CookieOptions): void;
33
+ /**
34
+ * Login handler helper
35
+ *
36
+ * Supports both modes:
37
+ * - Access-Only: Issues only access token, sets only access cookie
38
+ * - Access + Refresh: Issues token pair, sets both cookies
39
+ *
40
+ * Mode is determined by sdk.hasRefreshTokens()
41
+ *
42
+ * @param sdk - The Zapkey instance
43
+ * @param userId - User ID to issue tokens for
44
+ * @param res - Express response object
45
+ * @param options - Optional configuration
46
+ */
47
+ declare function handleLogin(sdk: Zapkey, userId: string, res: Response, options?: {
48
+ metadata?: Record<string, unknown>;
49
+ /**
50
+ * Override refresh token behavior
51
+ * Default: sdk.hasRefreshTokens()
52
+ * Set to false to force access-only mode even if refresh is enabled
53
+ */
54
+ withRefresh?: boolean;
55
+ }): Promise<void>;
56
+ /**
57
+ * Refresh token handler
58
+ *
59
+ * NOTE: Only works when refreshTokens is enabled.
60
+ * Returns 400 error if called when refresh tokens are disabled.
61
+ */
62
+ declare function handleRefresh(sdk: Zapkey, req: Request, res: Response): Promise<void>;
63
+ /**
64
+ * Logout handler
65
+ *
66
+ * Works in both modes:
67
+ * - Access-Only: Just clears access cookie
68
+ * - Access + Refresh: Revokes refresh token and clears both cookies
69
+ */
70
+ declare function handleLogout(sdk: Zapkey, req: Request, res: Response): Promise<void>;
71
+ /**
72
+ * Get user from request (helper)
73
+ */
74
+ declare function getUser(req: Request): any;
75
+
76
+ export { clearAuthCookie, getUser, handleLogin, handleLogout, handleRefresh, requireAuth, requireGuard, setAuthCookie };
@@ -0,0 +1,76 @@
1
+ import { Response, Request, RequestHandler } from 'express';
2
+ import { CookieOptions, Zapkey, Guard } from '@zapkey/core';
3
+
4
+ /**
5
+ * Express Adapter
6
+ *
7
+ * CRITICAL: This is a THIN adapter. NO business logic.
8
+ * Only request/response mapping.
9
+ *
10
+ * Supports both token modes:
11
+ * - Access-Only: Only access token in cookies, no refresh endpoint
12
+ * - Access + Refresh: Full token lifecycle with rotation
13
+ */
14
+
15
+ /**
16
+ * Middleware to verify access token from cookie or Authorization header
17
+ *
18
+ * Works in BOTH modes (access-only and access+refresh)
19
+ */
20
+ declare function requireAuth(sdk: Zapkey): RequestHandler;
21
+ /**
22
+ * Middleware to enforce authorization guards
23
+ */
24
+ declare function requireGuard(sdk: Zapkey, guard: Guard): RequestHandler;
25
+ /**
26
+ * Cookie helper - set cookie with SDK options
27
+ */
28
+ declare function setAuthCookie(res: Response, name: string, value: string, options: CookieOptions): void;
29
+ /**
30
+ * Cookie helper - clear auth cookie
31
+ */
32
+ declare function clearAuthCookie(res: Response, name: string, options: CookieOptions): void;
33
+ /**
34
+ * Login handler helper
35
+ *
36
+ * Supports both modes:
37
+ * - Access-Only: Issues only access token, sets only access cookie
38
+ * - Access + Refresh: Issues token pair, sets both cookies
39
+ *
40
+ * Mode is determined by sdk.hasRefreshTokens()
41
+ *
42
+ * @param sdk - The Zapkey instance
43
+ * @param userId - User ID to issue tokens for
44
+ * @param res - Express response object
45
+ * @param options - Optional configuration
46
+ */
47
+ declare function handleLogin(sdk: Zapkey, userId: string, res: Response, options?: {
48
+ metadata?: Record<string, unknown>;
49
+ /**
50
+ * Override refresh token behavior
51
+ * Default: sdk.hasRefreshTokens()
52
+ * Set to false to force access-only mode even if refresh is enabled
53
+ */
54
+ withRefresh?: boolean;
55
+ }): Promise<void>;
56
+ /**
57
+ * Refresh token handler
58
+ *
59
+ * NOTE: Only works when refreshTokens is enabled.
60
+ * Returns 400 error if called when refresh tokens are disabled.
61
+ */
62
+ declare function handleRefresh(sdk: Zapkey, req: Request, res: Response): Promise<void>;
63
+ /**
64
+ * Logout handler
65
+ *
66
+ * Works in both modes:
67
+ * - Access-Only: Just clears access cookie
68
+ * - Access + Refresh: Revokes refresh token and clears both cookies
69
+ */
70
+ declare function handleLogout(sdk: Zapkey, req: Request, res: Response): Promise<void>;
71
+ /**
72
+ * Get user from request (helper)
73
+ */
74
+ declare function getUser(req: Request): any;
75
+
76
+ export { clearAuthCookie, getUser, handleLogin, handleLogout, handleRefresh, requireAuth, requireGuard, setAuthCookie };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{AuthError as p,TokenError as R,OTPError as T,AuthorizationError as m,RateLimitError as g}from"@zapkey/core";function O(e){return async(s,o,t)=>{try{let n=e.cookie.getAccessTokenName(),i=s.cookies?.[n];if(!i){let a=s.headers.authorization;a?.startsWith("Bearer ")&&(i=a.substring(7))}if(!i){o.status(401).json({error:"Unauthorized",code:"NO_TOKEN"});return}let r=await e.token.verifyAccessToken(i);s.user=r,t()}catch(n){u(n,o)}}}function x(e,s){return async(o,t,n)=>{try{if(!e.authorization){t.status(500).json({error:"Authorization not configured",code:"AUTHORIZATION_NOT_CONFIGURED",hint:"Provide AuthorizationStore in SDK config to use requireGuard"});return}let i=o.user;if(!i){t.status(401).json({error:"Unauthorized",code:"NO_USER"});return}await e.authorization.guard.enforce(i.userId,s),n()}catch(i){u(i,t)}}}function c(e,s,o,t){let n={httpOnly:t.httpOnly,secure:t.secure,sameSite:t.sameSite,maxAge:t.maxAge*1e3,path:t.path,domain:t.domain};e.cookie(s,o,n)}function k(e,s,o){let t={httpOnly:o.httpOnly,secure:o.secure,sameSite:o.sameSite,maxAge:0,path:o.path,domain:o.domain};e.clearCookie(s,t)}async function y(e,s,o,t){let n=t?.withRefresh??e.hasRefreshTokens(),i=t?.metadata!==void 0?{userId:s,metadata:t.metadata}:{userId:s};if(n){let r=await e.token.issue(i),a=e.cookie.getAccessTokenCookieOptions(r.expiresIn),f=e.getConfig().token.refreshTokenExpiresIn,h=e.cookie.getRefreshTokenCookieOptions(f);c(o,e.cookie.getAccessTokenName(),r.accessToken,a),c(o,e.cookie.getRefreshTokenName(),r.refreshToken,h),o.json({success:!0,accessToken:r.accessToken,expiresIn:r.expiresIn,refreshEnabled:!0})}else{let r=await e.token.issueAccessToken(i),a=e.cookie.getAccessTokenCookieOptions(r.expiresIn);c(o,e.cookie.getAccessTokenName(),r.accessToken,a),o.json({success:!0,accessToken:r.accessToken,expiresIn:r.expiresIn,refreshEnabled:!1})}}async function l(e,s,o){try{if(!e.hasRefreshTokens()||!e.token.rotate){o.status(400).json({error:"Refresh tokens are not enabled",code:"REFRESH_DISABLED",hint:"Set refreshTokens: true in SDK config to enable token refresh"});return}let t=s.cookies?.[e.cookie.getRefreshTokenName()];if(!t){o.status(401).json({error:"No refresh token",code:"NO_REFRESH_TOKEN"});return}let n=await e.token.rotate(t),i=e.cookie.getAccessTokenCookieOptions(n.expiresIn),r=e.getConfig().token.refreshTokenExpiresIn,a=e.cookie.getRefreshTokenCookieOptions(r);c(o,e.cookie.getAccessTokenName(),n.accessToken,i),c(o,e.cookie.getRefreshTokenName(),n.refreshToken,a),o.json({success:!0,accessToken:n.accessToken,expiresIn:n.expiresIn})}catch(t){u(t,o)}}async function C(e,s,o){try{let t=e.cookie.getClearCookieOptions();if(k(o,e.cookie.getAccessTokenName(),t),e.hasRefreshTokens()&&e.token.revokeToken){let n=s.cookies?.[e.cookie.getRefreshTokenName()];n&&await e.token.revokeToken(n,"logout"),k(o,e.cookie.getRefreshTokenName(),t)}o.json({success:!0})}catch(t){u(t,o)}}function u(e,s){e instanceof g?s.status(e.statusCode).json({error:e.message,code:e.code,retryAfter:e.retryAfter}):e instanceof R?s.status(e.statusCode).json({error:e.message,code:e.code}):e instanceof T?s.status(e.statusCode).json({error:e.message,code:e.code}):e instanceof m?s.status(e.statusCode).json({error:e.message,code:e.code}):e instanceof p?s.status(e.statusCode).json({error:e.message,code:e.code}):s.status(500).json({error:"Internal server error",code:"INTERNAL_ERROR"})}function A(e){return e.user}export{k as clearAuthCookie,A as getUser,y as handleLogin,C as handleLogout,l as handleRefresh,O as requireAuth,x as requireGuard,c as setAuthCookie};
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@zapkey/express",
3
+ "version": "1.0.0",
4
+ "description": "Express adapter for ZapKey authentication SDK",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "test": "vitest run",
23
+ "lint": "eslint src --ext .ts",
24
+ "typecheck": "tsc --noEmit",
25
+ "clean": "rimraf dist"
26
+ },
27
+ "keywords": [
28
+ "zapkey",
29
+ "express",
30
+ "authentication",
31
+ "adapter"
32
+ ],
33
+ "author": "",
34
+ "license": "MIT",
35
+ "peerDependencies": {
36
+ "@zapkey/core": "^1.0.0",
37
+ "express": "^4.18.0 || ^5.0.0"
38
+ },
39
+ "devDependencies": {
40
+ "@zapkey/core": "^1.0.0",
41
+ "@types/express": "^5.0.6",
42
+ "@types/node": "^20.11.5",
43
+ "express": "^5.0.0",
44
+ "rimraf": "^5.0.5",
45
+ "tsup": "^8.0.1",
46
+ "typescript": "^5.3.3",
47
+ "vitest": "^1.2.0"
48
+ },
49
+ "engines": {
50
+ "node": ">=18.0.0"
51
+ }
52
+ }