@bhandari88/express-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,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ValidatorService = exports.PasswordService = exports.JwtService = void 0;
4
+ var jwt_1 = require("./jwt");
5
+ Object.defineProperty(exports, "JwtService", { enumerable: true, get: function () { return jwt_1.JwtService; } });
6
+ var password_1 = require("./password");
7
+ Object.defineProperty(exports, "PasswordService", { enumerable: true, get: function () { return password_1.PasswordService; } });
8
+ var validator_1 = require("./validator");
9
+ Object.defineProperty(exports, "ValidatorService", { enumerable: true, get: function () { return validator_1.ValidatorService; } });
@@ -0,0 +1,15 @@
1
+ import { JwtPayload, AuthConfig, AuthTokens } from '../types';
2
+ export declare class JwtService {
3
+ private accessTokenSecret;
4
+ private refreshTokenSecret;
5
+ private accessTokenExpiresIn;
6
+ private refreshTokenExpiresIn;
7
+ private enableRefreshTokens;
8
+ constructor(config: AuthConfig);
9
+ generateAccessToken(payload: Omit<JwtPayload, 'type' | 'iat' | 'exp'>): string;
10
+ generateRefreshToken(payload: Omit<JwtPayload, 'type' | 'iat' | 'exp'>): string;
11
+ generateTokens(payload: Omit<JwtPayload, 'type' | 'iat' | 'exp'>): AuthTokens;
12
+ verifyAccessToken(token: string): JwtPayload;
13
+ verifyRefreshToken(token: string): JwtPayload;
14
+ private getExpiresInSeconds;
15
+ }
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.JwtService = void 0;
37
+ const jwt = __importStar(require("jsonwebtoken"));
38
+ class JwtService {
39
+ constructor(config) {
40
+ this.accessTokenSecret = config.jwtSecret;
41
+ this.refreshTokenSecret = config.refreshTokenSecret || config.jwtSecret;
42
+ this.accessTokenExpiresIn = config.jwtExpiresIn || '15m';
43
+ this.refreshTokenExpiresIn = config.refreshTokenExpiresIn || '7d';
44
+ this.enableRefreshTokens = config.enableRefreshTokens ?? true;
45
+ }
46
+ generateAccessToken(payload) {
47
+ return jwt.sign({ ...payload, type: 'access' }, this.accessTokenSecret, { expiresIn: this.accessTokenExpiresIn });
48
+ }
49
+ generateRefreshToken(payload) {
50
+ if (!this.enableRefreshTokens) {
51
+ throw new Error('Refresh tokens are not enabled');
52
+ }
53
+ return jwt.sign({ ...payload, type: 'refresh' }, this.refreshTokenSecret, { expiresIn: this.refreshTokenExpiresIn });
54
+ }
55
+ generateTokens(payload) {
56
+ const accessToken = this.generateAccessToken(payload);
57
+ const tokens = {
58
+ accessToken,
59
+ expiresIn: this.getExpiresInSeconds(this.accessTokenExpiresIn),
60
+ };
61
+ if (this.enableRefreshTokens) {
62
+ tokens.refreshToken = this.generateRefreshToken(payload);
63
+ }
64
+ return tokens;
65
+ }
66
+ verifyAccessToken(token) {
67
+ try {
68
+ const decoded = jwt.verify(token, this.accessTokenSecret);
69
+ if (decoded.type !== 'access') {
70
+ throw new Error('Invalid token type');
71
+ }
72
+ return decoded;
73
+ }
74
+ catch (error) {
75
+ if (error instanceof jwt.TokenExpiredError) {
76
+ throw new Error('Access token expired');
77
+ }
78
+ if (error instanceof jwt.JsonWebTokenError) {
79
+ throw new Error('Invalid access token');
80
+ }
81
+ throw error;
82
+ }
83
+ }
84
+ verifyRefreshToken(token) {
85
+ if (!this.enableRefreshTokens) {
86
+ throw new Error('Refresh tokens are not enabled');
87
+ }
88
+ try {
89
+ const decoded = jwt.verify(token, this.refreshTokenSecret);
90
+ if (decoded.type !== 'refresh') {
91
+ throw new Error('Invalid token type');
92
+ }
93
+ return decoded;
94
+ }
95
+ catch (error) {
96
+ if (error instanceof jwt.TokenExpiredError) {
97
+ throw new Error('Refresh token expired');
98
+ }
99
+ if (error instanceof jwt.JsonWebTokenError) {
100
+ throw new Error('Invalid refresh token');
101
+ }
102
+ throw error;
103
+ }
104
+ }
105
+ getExpiresInSeconds(expiresIn) {
106
+ const match = expiresIn.match(/^(\d+)([smhd])$/);
107
+ if (!match)
108
+ return 900; // Default 15 minutes
109
+ const value = parseInt(match[1]);
110
+ const unit = match[2];
111
+ switch (unit) {
112
+ case 's': return value;
113
+ case 'm': return value * 60;
114
+ case 'h': return value * 60 * 60;
115
+ case 'd': return value * 24 * 60 * 60;
116
+ default: return 900;
117
+ }
118
+ }
119
+ }
120
+ exports.JwtService = JwtService;
@@ -0,0 +1,14 @@
1
+ import { AuthConfig } from '../types';
2
+ export declare class PasswordService {
3
+ private keylen;
4
+ private saltLength;
5
+ private cost;
6
+ constructor(config: AuthConfig);
7
+ private scryptAsync;
8
+ hash(password: string): Promise<string>;
9
+ compare(password: string, hashedPassword: string): Promise<boolean>;
10
+ validatePassword(password: string): {
11
+ valid: boolean;
12
+ message?: string;
13
+ };
14
+ }
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.PasswordService = void 0;
37
+ const crypto = __importStar(require("crypto"));
38
+ class PasswordService {
39
+ constructor(config) {
40
+ // Use keylen instead of rounds for scrypt
41
+ // scrypt is memory-hard and more secure than bcrypt
42
+ this.keylen = 64; // 64 bytes = 512 bits
43
+ this.saltLength = 32; // 32 bytes = 256 bits
44
+ this.cost = config.bcryptRounds || 16384; // CPU/memory cost parameter (2^14)
45
+ }
46
+ async scryptAsync(password, salt, keylen, options) {
47
+ return new Promise((resolve, reject) => {
48
+ crypto.scrypt(password, salt, keylen, options || {}, (err, derivedKey) => {
49
+ if (err)
50
+ reject(err);
51
+ else
52
+ resolve(derivedKey);
53
+ });
54
+ });
55
+ }
56
+ async hash(password) {
57
+ if (!password || password.length < 6) {
58
+ throw new Error('Password must be at least 6 characters long');
59
+ }
60
+ // Generate random salt
61
+ const salt = crypto.randomBytes(this.saltLength);
62
+ // Hash password using scrypt
63
+ const hash = await this.scryptAsync(password, salt, this.keylen, { N: this.cost });
64
+ // Return salt:hash as base64 encoded string
65
+ const saltBase64 = salt.toString('base64');
66
+ const hashBase64 = hash.toString('base64');
67
+ return `${saltBase64}:${hashBase64}`;
68
+ }
69
+ async compare(password, hashedPassword) {
70
+ try {
71
+ // Extract salt and hash from stored string (format: salt:hash)
72
+ const [saltBase64, hashBase64] = hashedPassword.split(':');
73
+ if (!saltBase64 || !hashBase64) {
74
+ return false;
75
+ }
76
+ const salt = Buffer.from(saltBase64, 'base64');
77
+ const storedHash = Buffer.from(hashBase64, 'base64');
78
+ // Hash the provided password with the extracted salt
79
+ const derivedHash = await this.scryptAsync(password, salt, this.keylen, { N: this.cost });
80
+ // Use timing-safe comparison to prevent timing attacks
81
+ if (storedHash.length !== derivedHash.length) {
82
+ return false;
83
+ }
84
+ return crypto.timingSafeEqual(storedHash, derivedHash);
85
+ }
86
+ catch (error) {
87
+ return false;
88
+ }
89
+ }
90
+ validatePassword(password) {
91
+ if (!password) {
92
+ return { valid: false, message: 'Password is required' };
93
+ }
94
+ if (password.length < 6) {
95
+ return { valid: false, message: 'Password must be at least 6 characters long' };
96
+ }
97
+ if (password.length > 128) {
98
+ return { valid: false, message: 'Password must be less than 128 characters' };
99
+ }
100
+ return { valid: true };
101
+ }
102
+ }
103
+ exports.PasswordService = PasswordService;
@@ -0,0 +1,25 @@
1
+ export declare class ValidatorService {
2
+ static validateEmail(email: string): boolean;
3
+ static validatePhone(phone: string): boolean;
4
+ static validateUsername(username: string): boolean;
5
+ static sanitizeEmail(email: string): string;
6
+ static sanitizePhone(phone: string): string;
7
+ static validateLoginCredentials(credentials: {
8
+ email?: string;
9
+ username?: string;
10
+ phone?: string;
11
+ password: string;
12
+ }): {
13
+ valid: boolean;
14
+ message?: string;
15
+ };
16
+ static validateRegisterData(data: {
17
+ email?: string;
18
+ username?: string;
19
+ phone?: string;
20
+ password: string;
21
+ }): {
22
+ valid: boolean;
23
+ message?: string;
24
+ };
25
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ValidatorService = void 0;
7
+ const validator_1 = __importDefault(require("validator"));
8
+ class ValidatorService {
9
+ static validateEmail(email) {
10
+ return validator_1.default.isEmail(email);
11
+ }
12
+ static validatePhone(phone) {
13
+ // Allow international format with + prefix
14
+ return validator_1.default.isMobilePhone(phone.replace(/\s/g, ''), 'any', { strictMode: false });
15
+ }
16
+ static validateUsername(username) {
17
+ // Username: 3-30 characters, alphanumeric, underscore, hyphen
18
+ return /^[a-zA-Z0-9_-]{3,30}$/.test(username);
19
+ }
20
+ static sanitizeEmail(email) {
21
+ return validator_1.default.normalizeEmail(email) || email;
22
+ }
23
+ static sanitizePhone(phone) {
24
+ // Remove spaces and ensure proper format
25
+ return phone.replace(/\s/g, '').replace(/^(\d{10})$/, '+1$1'); // Add country code if missing
26
+ }
27
+ static validateLoginCredentials(credentials) {
28
+ const { email, username, phone, password } = credentials;
29
+ // At least one identifier must be provided
30
+ if (!email && !username && !phone) {
31
+ return { valid: false, message: 'Email, username, or phone is required' };
32
+ }
33
+ // Password is required
34
+ if (!password) {
35
+ return { valid: false, message: 'Password is required' };
36
+ }
37
+ // Validate email if provided
38
+ if (email && !this.validateEmail(email)) {
39
+ return { valid: false, message: 'Invalid email format' };
40
+ }
41
+ // Validate phone if provided
42
+ if (phone && !this.validatePhone(phone)) {
43
+ return { valid: false, message: 'Invalid phone format' };
44
+ }
45
+ // Validate username if provided
46
+ if (username && !this.validateUsername(username)) {
47
+ return { valid: false, message: 'Username must be 3-30 characters and contain only letters, numbers, underscores, and hyphens' };
48
+ }
49
+ return { valid: true };
50
+ }
51
+ static validateRegisterData(data) {
52
+ // At least one identifier must be provided
53
+ if (!data.email && !data.username && !data.phone) {
54
+ return { valid: false, message: 'At least one of email, username, or phone is required' };
55
+ }
56
+ const validation = this.validateLoginCredentials(data);
57
+ if (!validation.valid) {
58
+ return validation;
59
+ }
60
+ return { valid: true };
61
+ }
62
+ }
63
+ exports.ValidatorService = ValidatorService;
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@bhandari88/express-auth",
3
+ "version": "1.0.0",
4
+ "description": "Plug-and-play authentication handler for Express.js with TypeScript supporting email, username, phone, and social login",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "tsc --watch",
10
+ "prepublishOnly": "npm run build"
11
+ },
12
+ "keywords": [
13
+ "authentication",
14
+ "auth",
15
+ "express",
16
+ "jwt",
17
+ "typescript",
18
+ "social-login",
19
+ "passport"
20
+ ],
21
+ "author": "",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "jsonwebtoken": "^9.0.2",
25
+ "passport": "^0.7.0",
26
+ "passport-google-oauth20": "^2.0.0",
27
+ "passport-facebook": "^3.0.0",
28
+ "validator": "^13.11.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/express": "^4.17.21",
32
+ "@types/jsonwebtoken": "^9.0.5",
33
+ "@types/passport": "^1.0.16",
34
+ "@types/passport-google-oauth20": "^2.0.14",
35
+ "@types/passport-facebook": "^3.0.3",
36
+ "@types/validator": "^13.11.7",
37
+ "@types/node": "^20.10.5",
38
+ "typescript": "^5.3.3"
39
+ },
40
+ "peerDependencies": {
41
+ "express": "^4.18.0"
42
+ },
43
+ "peerDependenciesMeta": {
44
+ "express": {
45
+ "optional": false
46
+ }
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": ""
54
+ },
55
+ "files": [
56
+ "dist/**/*",
57
+ "README.md",
58
+ "INSTALLATION.md"
59
+ ]
60
+ }