@soapjs/soap-auth 0.3.3 → 0.4.4

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.
Files changed (98) hide show
  1. package/.claude/settings.local.json +20 -0
  2. package/build/errors.d.ts +1 -1
  3. package/build/errors.js +2 -2
  4. package/build/index.d.ts +1 -0
  5. package/build/index.js +1 -0
  6. package/build/services/auth-throttle.service.d.ts +2 -2
  7. package/build/services/auth-throttle.service.js +2 -2
  8. package/build/services/index.d.ts +1 -0
  9. package/build/services/index.js +1 -0
  10. package/build/services/password.service.d.ts +7 -5
  11. package/build/services/password.service.js +76 -18
  12. package/build/services/totp.service.d.ts +16 -0
  13. package/build/services/totp.service.js +96 -0
  14. package/build/session/session-handler.d.ts +1 -0
  15. package/build/session/session-handler.js +39 -6
  16. package/build/soap-auth.d.ts +13 -6
  17. package/build/soap-auth.js +132 -5
  18. package/build/strategies/api-key/api-key.strategy.d.ts +4 -4
  19. package/build/strategies/api-key/api-key.strategy.js +3 -2
  20. package/build/strategies/base-auth.strategy.d.ts +5 -4
  21. package/build/strategies/basic/basic.strategy.d.ts +2 -1
  22. package/build/strategies/basic/basic.strategy.js +1 -0
  23. package/build/strategies/credential-auth.strategy.d.ts +7 -7
  24. package/build/strategies/credential-auth.strategy.js +9 -14
  25. package/build/strategies/index.d.ts +1 -0
  26. package/build/strategies/index.js +1 -0
  27. package/build/strategies/jwt/jwt.strategy.d.ts +3 -1
  28. package/build/strategies/jwt/jwt.strategy.js +41 -9
  29. package/build/strategies/jwt/jwt.tools.js +16 -14
  30. package/build/strategies/local/local.strategy.d.ts +6 -3
  31. package/build/strategies/local/local.strategy.js +83 -2
  32. package/build/strategies/oauth2/hybrid.oauth2.strategy.d.ts +3 -3
  33. package/build/strategies/oauth2/hybrid.oauth2.strategy.js +1 -6
  34. package/build/strategies/oauth2/oauth2.errors.d.ts +1 -0
  35. package/build/strategies/oauth2/oauth2.errors.js +4 -0
  36. package/build/strategies/oauth2/oauth2.strategy.d.ts +6 -4
  37. package/build/strategies/oauth2/oauth2.strategy.js +114 -46
  38. package/build/strategies/oauth2/oauth2.tools.js +2 -2
  39. package/build/strategies/oauth2/oauth2.types.d.ts +2 -2
  40. package/build/strategies/oauth2/providers/facebook.strategy.d.ts +11 -0
  41. package/build/strategies/oauth2/providers/facebook.strategy.js +58 -0
  42. package/build/strategies/oauth2/providers/github.strategy.d.ts +11 -0
  43. package/build/strategies/oauth2/providers/github.strategy.js +56 -0
  44. package/build/strategies/oauth2/providers/google.strategy.d.ts +11 -0
  45. package/build/strategies/oauth2/providers/google.strategy.js +52 -0
  46. package/build/strategies/oauth2/providers/http-oauth2.strategy.d.ts +16 -0
  47. package/build/strategies/oauth2/providers/http-oauth2.strategy.js +49 -0
  48. package/build/strategies/oauth2/providers/index.d.ts +5 -0
  49. package/build/strategies/oauth2/providers/index.js +21 -0
  50. package/build/strategies/oauth2/providers/provider.types.d.ts +7 -0
  51. package/build/strategies/oauth2/providers/provider.types.js +2 -0
  52. package/build/strategies/token-auth.strategy.d.ts +4 -4
  53. package/build/strategies/token-auth.strategy.js +2 -3
  54. package/build/tools/tools.js +1 -2
  55. package/build/types.d.ts +31 -32
  56. package/build/utils/validation.d.ts +23 -0
  57. package/build/utils/validation.js +139 -0
  58. package/package.json +8 -7
  59. package/build/__tests__/soap-auth.test.d.ts +0 -1
  60. package/build/__tests__/soap-auth.test.js +0 -42
  61. package/build/services/__tests__/account-lock.service.test.d.ts +0 -1
  62. package/build/services/__tests__/account-lock.service.test.js +0 -55
  63. package/build/services/__tests__/auth-throttle.service.test.d.ts +0 -1
  64. package/build/services/__tests__/auth-throttle.service.test.js +0 -48
  65. package/build/services/__tests__/jwks.service.test.d.ts +0 -1
  66. package/build/services/__tests__/jwks.service.test.js +0 -39
  67. package/build/services/__tests__/mfa.service.test.d.ts +0 -1
  68. package/build/services/__tests__/mfa.service.test.js +0 -66
  69. package/build/services/__tests__/password.service.test.d.ts +0 -1
  70. package/build/services/__tests__/password.service.test.js +0 -66
  71. package/build/services/__tests__/pkce.service.test.d.ts +0 -1
  72. package/build/services/__tests__/pkce.service.test.js +0 -77
  73. package/build/services/__tests__/rate-limit.service.test.d.ts +0 -1
  74. package/build/services/__tests__/rate-limit.service.test.js +0 -37
  75. package/build/services/__tests__/role.service.test.d.ts +0 -1
  76. package/build/services/__tests__/role.service.test.js +0 -31
  77. package/build/session/__tests__/file.session-store.test.d.ts +0 -1
  78. package/build/session/__tests__/file.session-store.test.js +0 -117
  79. package/build/session/__tests__/memory.session-store.test.d.ts +0 -1
  80. package/build/session/__tests__/memory.session-store.test.js +0 -77
  81. package/build/session/__tests__/session-handler.test.d.ts +0 -1
  82. package/build/session/__tests__/session-handler.test.js +0 -337
  83. package/build/strategies/__tests__/base-auth.strategy.test.d.ts +0 -14
  84. package/build/strategies/__tests__/base-auth.strategy.test.js +0 -137
  85. package/build/strategies/__tests__/credential-auth.strategy.test.d.ts +0 -14
  86. package/build/strategies/__tests__/credential-auth.strategy.test.js +0 -265
  87. package/build/strategies/__tests__/token-auth.strategy.test.d.ts +0 -28
  88. package/build/strategies/__tests__/token-auth.strategy.test.js +0 -298
  89. package/build/strategies/api-key/__tests__/api-key.strategy.test.d.ts +0 -1
  90. package/build/strategies/api-key/__tests__/api-key.strategy.test.js +0 -103
  91. package/build/strategies/basic/__tests__/basic.strategy.test.d.ts +0 -1
  92. package/build/strategies/basic/__tests__/basic.strategy.test.js +0 -104
  93. package/build/strategies/jwt/__tests__/jwt.strategy.test.d.ts +0 -1
  94. package/build/strategies/jwt/__tests__/jwt.strategy.test.js +0 -156
  95. package/build/strategies/jwt/__tests__/jwt.tools.test.d.ts +0 -1
  96. package/build/strategies/jwt/__tests__/jwt.tools.test.js +0 -98
  97. package/build/strategies/local/__tests__/local.strategy.test.d.ts +0 -1
  98. package/build/strategies/local/__tests__/local.strategy.test.js +0 -115
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GoogleStrategy = void 0;
4
+ const http_oauth2_strategy_1 = require("./http-oauth2.strategy");
5
+ const GOOGLE_ENDPOINTS = {
6
+ authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",
7
+ tokenUrl: "https://oauth2.googleapis.com/token",
8
+ userInfoUrl: "https://openidconnect.googleapis.com/v1/userinfo",
9
+ revocationUrl: "https://oauth2.googleapis.com/revoke",
10
+ };
11
+ class GoogleStrategy extends http_oauth2_strategy_1.HttpOAuth2Strategy {
12
+ name = "google";
13
+ constructor(config, session, jwt, logger) {
14
+ super({
15
+ grantType: "authorization_code",
16
+ scope: config.scope ?? ["openid", "email", "profile"],
17
+ routes: {
18
+ login: { path: "/auth/google", method: "GET" },
19
+ callback: { path: "/auth/google/callback", method: "GET" },
20
+ logout: { path: "/auth/google/logout", method: "POST" },
21
+ ...config.routes,
22
+ },
23
+ ...config,
24
+ endpoints: { ...GOOGLE_ENDPOINTS, ...config.endpoints },
25
+ }, session, jwt, logger);
26
+ }
27
+ async fetchUser(accessToken) {
28
+ try {
29
+ const response = await fetch(GOOGLE_ENDPOINTS.userInfoUrl, {
30
+ headers: { Authorization: `Bearer ${accessToken}` },
31
+ });
32
+ if (!response.ok)
33
+ return null;
34
+ const profile = await response.json();
35
+ if (this.config.user?.validateUser) {
36
+ return this.config.user.validateUser(profile);
37
+ }
38
+ return {
39
+ id: profile.sub,
40
+ email: profile.email,
41
+ username: profile.name,
42
+ picture: profile.picture,
43
+ emailVerified: profile.email_verified,
44
+ };
45
+ }
46
+ catch (error) {
47
+ this.logger?.error("GoogleStrategy.fetchUser failed:", error);
48
+ return null;
49
+ }
50
+ }
51
+ }
52
+ exports.GoogleStrategy = GoogleStrategy;
@@ -0,0 +1,16 @@
1
+ import * as Soap from "@soapjs/soap";
2
+ import { OAuth2Strategy } from "../oauth2.strategy";
3
+ import { OAuth2StrategyConfig } from "../oauth2.types";
4
+ import { SessionHandler } from "../../../session/session-handler";
5
+ import { JwtStrategy } from "../../jwt/jwt.strategy";
6
+ export declare abstract class HttpOAuth2Strategy<TUser extends Soap.AuthUser = Soap.AuthUser> extends OAuth2Strategy<Soap.HttpContext, TUser> {
7
+ constructor(config: OAuth2StrategyConfig<Soap.HttpContext, TUser>, session?: SessionHandler, jwt?: JwtStrategy<Soap.HttpContext, TUser>, logger?: Soap.Logger);
8
+ protected extractAccessToken(ctx: Soap.HttpContext): Promise<string | undefined>;
9
+ protected extractRefreshToken(ctx: Soap.HttpContext): Promise<string | undefined>;
10
+ protected storeAccessToken(_token: string, _ctx: Soap.HttpContext): Promise<void>;
11
+ protected storeRefreshToken(_token: string, _ctx: Soap.HttpContext): Promise<void>;
12
+ protected embedAccessToken(token: string, ctx: Soap.HttpContext): void;
13
+ protected embedRefreshToken(token: string, ctx: Soap.HttpContext): void;
14
+ protected extractAuthorizationCode(ctx: Soap.HttpContext): string | null;
15
+ protected redirectUser(ctx: Soap.HttpContext, authUrl: string): void;
16
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HttpOAuth2Strategy = void 0;
4
+ const oauth2_strategy_1 = require("../oauth2.strategy");
5
+ class HttpOAuth2Strategy extends oauth2_strategy_1.OAuth2Strategy {
6
+ constructor(config, session, jwt, logger) {
7
+ super(config, session, jwt, logger);
8
+ }
9
+ extractAccessToken(ctx) {
10
+ const auth = ctx.req.headers?.authorization;
11
+ if (typeof auth === "string" && auth.startsWith("Bearer ")) {
12
+ return Promise.resolve(auth.slice(7));
13
+ }
14
+ return Promise.resolve(ctx.req.cookies?.access_token);
15
+ }
16
+ extractRefreshToken(ctx) {
17
+ return Promise.resolve(ctx.req.cookies?.refresh_token);
18
+ }
19
+ storeAccessToken(_token, _ctx) {
20
+ return Promise.resolve();
21
+ }
22
+ storeRefreshToken(_token, _ctx) {
23
+ return Promise.resolve();
24
+ }
25
+ embedAccessToken(token, ctx) {
26
+ ctx.res.setHeader("Authorization", `Bearer ${token}`);
27
+ }
28
+ embedRefreshToken(token, ctx) {
29
+ ctx.res.cookie("refresh_token", token, {
30
+ httpOnly: true,
31
+ secure: true,
32
+ sameSite: "lax",
33
+ maxAge: 30 * 24 * 60 * 60 * 1000,
34
+ });
35
+ }
36
+ extractAuthorizationCode(ctx) {
37
+ return ctx.req.query?.code ?? null;
38
+ }
39
+ redirectUser(ctx, authUrl) {
40
+ if (typeof ctx.res.redirect === "function") {
41
+ ctx.res.redirect(authUrl);
42
+ }
43
+ else {
44
+ ctx.res.setHeader("Location", authUrl);
45
+ ctx.res.status(302).json({ message: "Redirecting", location: authUrl });
46
+ }
47
+ }
48
+ }
49
+ exports.HttpOAuth2Strategy = HttpOAuth2Strategy;
@@ -0,0 +1,5 @@
1
+ export * from "./http-oauth2.strategy";
2
+ export * from "./google.strategy";
3
+ export * from "./github.strategy";
4
+ export * from "./facebook.strategy";
5
+ export * from "./provider.types";
@@ -0,0 +1,21 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./http-oauth2.strategy"), exports);
18
+ __exportStar(require("./google.strategy"), exports);
19
+ __exportStar(require("./github.strategy"), exports);
20
+ __exportStar(require("./facebook.strategy"), exports);
21
+ __exportStar(require("./provider.types"), exports);
@@ -0,0 +1,7 @@
1
+ import * as Soap from "@soapjs/soap";
2
+ import { OAuth2StrategyConfig, OAuth2Endpoints } from "../oauth2.types";
3
+ export interface SocialProviderConfig<TUser extends Soap.AuthUser = Soap.AuthUser> extends Omit<OAuth2StrategyConfig<Soap.HttpContext, TUser>, "endpoints" | "grantType" | "routes"> {
4
+ grantType?: "authorization_code";
5
+ endpoints?: Partial<OAuth2Endpoints>;
6
+ routes?: OAuth2StrategyConfig<Soap.HttpContext, TUser>["routes"];
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,8 +1,8 @@
1
1
  import * as Soap from "@soapjs/soap";
2
- import { AuthResult, TokenAuthStrategyConfig } from "../types";
2
+ import { TokenAuthStrategyConfig } from "../types";
3
3
  import { BaseAuthStrategy } from "./base-auth.strategy";
4
4
  import { SessionHandler } from "../session/session-handler";
5
- export declare abstract class TokenAuthStrategy<TContext = unknown, TUser = unknown> extends BaseAuthStrategy<TContext, TUser> {
5
+ export declare abstract class TokenAuthStrategy<TContext = Soap.HttpContext, TUser extends Soap.AuthUser = Soap.AuthUser> extends BaseAuthStrategy<TContext, TUser> {
6
6
  protected config: TokenAuthStrategyConfig<TContext, TUser>;
7
7
  protected session?: SessionHandler;
8
8
  protected logger?: Soap.Logger;
@@ -20,9 +20,9 @@ export declare abstract class TokenAuthStrategy<TContext = unknown, TUser = unkn
20
20
  protected abstract embedAccessToken(token: string, context: TContext): void;
21
21
  protected abstract embedRefreshToken(token: string, context: TContext): void;
22
22
  protected fetchUser(payload: unknown): Promise<TUser | null>;
23
- authenticate(context: TContext): Promise<AuthResult<TUser>>;
23
+ authenticate(context: TContext): Promise<Soap.AuthResult<TUser> | null>;
24
24
  private verifyAndFetchUser;
25
- refreshTokens(context: TContext, existingUser?: TUser): Promise<AuthResult<TUser>>;
25
+ refreshTokens(context: TContext, existingUser?: TUser): Promise<Soap.AuthResult<TUser>>;
26
26
  issueTokens(user: TUser, context: TContext, rotate?: boolean): Promise<{
27
27
  accessToken: string;
28
28
  refreshToken: any;
@@ -145,12 +145,11 @@ class TokenAuthStrategy extends base_auth_strategy_1.BaseAuthStrategy {
145
145
  const accessToken = await this.generateAccessToken(user, context);
146
146
  let refreshToken;
147
147
  if (this.config.refreshToken) {
148
- refreshToken = await this.generateRefreshToken(user, context);
149
- if (rotate && this.config?.refreshToken?.rotation) {
148
+ if (rotate && this.config.refreshToken.rotation) {
150
149
  const oldRefreshToken = this.extractRefreshToken(context);
151
150
  refreshToken = await this.rotateToken(oldRefreshToken, user, context);
152
151
  }
153
- else if (this.generateRefreshToken) {
152
+ else {
154
153
  refreshToken = await this.generateRefreshToken(user, context);
155
154
  }
156
155
  }
@@ -4,10 +4,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.generateRandomString = exports.resolveConfig = exports.generateUUID = void 0;
7
- const uuid_1 = require("uuid");
8
7
  const crypto_1 = __importDefault(require("crypto"));
9
8
  const generateUUID = async () => {
10
- return Promise.resolve((0, uuid_1.v4)());
9
+ return Promise.resolve(crypto_1.default.randomUUID());
11
10
  };
12
11
  exports.generateUUID = generateUUID;
13
12
  function resolveConfig(strategyConfig, globalConfig) {
package/build/types.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import * as Soap from "@soapjs/soap";
2
+ export type AuthResult<TUser extends Soap.AuthUser = Soap.AuthUser> = Soap.AuthResult<TUser>;
3
+ export type AuthStrategy<TUser extends Soap.AuthUser = Soap.AuthUser> = Soap.AuthStrategy<TUser>;
2
4
  import { LocalStrategyConfig } from "./strategies/local/local.types";
3
5
  import { OAuth2StrategyConfig } from "./strategies/oauth2/oauth2.types";
4
6
  import { ApiKeyStrategyConfig } from "./strategies/api-key/api-key.types";
@@ -71,29 +73,43 @@ export interface MfaConfig<TUser = unknown, TContext = unknown> {
71
73
  resetMfaAttempts?: (user: TUser) => Promise<void>;
72
74
  incrementMfaAttempts?: (user: TUser) => Promise<void>;
73
75
  }
76
+ export type PasswordType = "default" | "one-time" | "temporary";
77
+ export type NewPasswordOptions = {
78
+ expiresIn?: number;
79
+ type: PasswordType;
80
+ additional?: Record<string, unknown>;
81
+ };
82
+ export type PasswordInfo = {
83
+ type: PasswordType;
84
+ expiresIn?: number;
85
+ lastChangeDate?: Date;
86
+ };
74
87
  export interface PasswordPolicyConfig {
75
- validatePassword?: (password: string) => Promise<boolean>;
76
- getLastPasswordChange?: (identifier: string) => Promise<Date>;
88
+ validatePassword?: (password: string, previousPassword?: string) => Promise<void>;
89
+ getPasswordPasswordInfo?: (identifier: string) => Promise<PasswordInfo>;
77
90
  passwordExpirationDays?: number;
78
91
  generateResetToken?: (identifier: string) => Promise<string>;
79
- sendResetEmail?: (identifier: string, token: string) => Promise<void>;
80
- validateResetToken?: (token: string) => Promise<boolean>;
81
- updatePassword?: (identifier: string, newPassword: string) => Promise<void>;
92
+ sendPasswordResetEmail?: (identifier: string, token: string) => Promise<void>;
93
+ validatePasswordResetToken?: (token: string) => Promise<boolean>;
94
+ updatePassword?: (identifier: string, newPassword: string, options?: NewPasswordOptions) => Promise<void>;
95
+ generatePassword?: (identifier: string, options: NewPasswordOptions) => Promise<string>;
82
96
  }
83
97
  export interface UserConfig<TUser = unknown> {
84
98
  fetchUser: (payload: unknown) => Promise<TUser | null>;
85
99
  validateUser?: (payload: unknown) => Promise<any>;
86
100
  }
87
- export interface CredentailsConfig<TContext = any> {
101
+ export type CredentailsConfig<TContext = any> = CredentialsConfig<TContext>;
102
+ export interface CredentialsConfig<TContext = any> {
88
103
  extractCredentials: <TCredentials = {
89
104
  identifier: string;
90
105
  password: string;
106
+ newPassword?: string;
91
107
  }>(context: TContext) => TCredentials;
92
108
  verifyCredentials: (identifier: string, password: string) => Promise<boolean>;
93
109
  }
94
110
  export interface CredentialAuthStrategyConfig<TContext = unknown, TUser = unknown> extends BaseAuthStrategyConfig<TContext, TUser> {
95
111
  passwordPolicy?: PasswordPolicyConfig;
96
- credentials?: CredentailsConfig<TContext>;
112
+ credentials?: CredentialsConfig<TContext>;
97
113
  jwt?: TokenAuthConfig<TContext>;
98
114
  user?: UserConfig<TUser>;
99
115
  allowGuest?: boolean;
@@ -155,43 +171,26 @@ export type AuthFailureContext<TContext = unknown> = {
155
171
  email?: string;
156
172
  additional?: Record<string, unknown>;
157
173
  };
158
- export type AuthResult<TUser = unknown, TSessionData = unknown> = {
159
- user: TUser;
160
- session?: SessionInfo<TSessionData>;
161
- tokens?: {
162
- accessToken?: string;
163
- refreshToken?: string | null;
164
- apiKey?: string | null;
165
- [key: string]: string;
166
- };
167
- };
168
- export interface AuthStrategy<TContext = unknown, TUser = unknown> {
169
- init?(...args: unknown[]): Promise<void>;
170
- authenticate(context?: TContext, ...args: unknown[]): Promise<AuthResult<TUser> | null>;
171
- authorize?(user: any, action: string, resource?: string): Promise<boolean>;
172
- refresh?(refreshToken: string): Promise<string>;
173
- logout?(context?: TContext): Promise<void>;
174
- logout?(context?: TContext): Promise<void>;
175
- }
176
- export interface SoapHttpAuthConfig<TContext = unknown, TUser = unknown> {
174
+ export interface SoapHttpAuthConfig<TContext = unknown, TUser extends Soap.AuthUser = Soap.AuthUser> {
177
175
  local?: LocalStrategyConfig<TContext, TUser>;
176
+ jwt?: JwtConfig<TContext, TUser>;
178
177
  oauth2?: {
179
178
  [provider: string]: OAuth2StrategyConfig<TContext, TUser>;
180
179
  };
181
180
  apiKey?: ApiKeyStrategyConfig<TContext, TUser>;
182
181
  basic?: BasicStrategyConfig<TContext, TUser>;
183
- custom: {
184
- [label: string]: AuthStrategy;
182
+ custom?: {
183
+ [label: string]: Soap.AuthStrategy;
185
184
  };
186
185
  }
187
- export interface SoapSocketAuthConfig<TContext = unknown, TUser = unknown> {
186
+ export interface SoapSocketAuthConfig<TContext = unknown, TUser extends Soap.AuthUser = Soap.AuthUser> {
188
187
  jwt?: JwtConfig<TContext, TUser>;
189
188
  apiKey?: ApiKeyStrategyConfig<TContext, TUser>;
190
- custom: {
191
- [provider: string]: AuthStrategy;
189
+ custom?: {
190
+ [provider: string]: Soap.AuthStrategy;
192
191
  };
193
192
  }
194
- export interface SoapAuthConfig<TContext = unknown, TUser = unknown> {
193
+ export interface SoapAuthConfig<TContext = unknown, TUser extends Soap.AuthUser = Soap.AuthUser> {
195
194
  session?: SessionConfig;
196
195
  jwt?: TokenAuthConfig<TContext>;
197
196
  http?: SoapHttpAuthConfig<TContext, TUser>;
@@ -0,0 +1,23 @@
1
+ export declare class ValidationError extends Error {
2
+ field?: string;
3
+ constructor(message: string, field?: string);
4
+ }
5
+ export declare class ValidationUtils {
6
+ static required(value: any, fieldName: string): void;
7
+ static nonEmptyString(value: any, fieldName: string): string;
8
+ static email(value: any, fieldName: string): string;
9
+ static password(value: any, fieldName: string, minLength?: number): string;
10
+ static positiveNumber(value: any, fieldName: string): number;
11
+ static range(value: any, fieldName: string, min: number, max: number): number;
12
+ static oneOf<T>(value: any, fieldName: string, allowedValues: T[]): T;
13
+ static url(value: any, fieldName: string): string;
14
+ static jwtToken(value: any, fieldName: string): string;
15
+ static uuid(value: any, fieldName: string): string;
16
+ static object(value: any, fieldName: string): object;
17
+ static array(value: any, fieldName: string): any[];
18
+ static function(value: any, fieldName: string): Function;
19
+ static validateConfig(config: any, requiredFields: string[], fieldName?: string): void;
20
+ static pattern(value: any, fieldName: string, regex: RegExp, message?: string): string;
21
+ static date(value: any, fieldName: string): Date;
22
+ static boolean(value: any, fieldName: string): boolean;
23
+ }
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ValidationUtils = exports.ValidationError = void 0;
4
+ class ValidationError extends Error {
5
+ field;
6
+ constructor(message, field) {
7
+ super(message);
8
+ this.field = field;
9
+ this.name = "ValidationError";
10
+ }
11
+ }
12
+ exports.ValidationError = ValidationError;
13
+ class ValidationUtils {
14
+ static required(value, fieldName) {
15
+ if (value === null || value === undefined) {
16
+ throw new ValidationError(`${fieldName} is required`, fieldName);
17
+ }
18
+ }
19
+ static nonEmptyString(value, fieldName) {
20
+ this.required(value, fieldName);
21
+ if (typeof value !== "string" || value.trim().length === 0) {
22
+ throw new ValidationError(`${fieldName} must be a non-empty string`, fieldName);
23
+ }
24
+ return value.trim();
25
+ }
26
+ static email(value, fieldName) {
27
+ const email = this.nonEmptyString(value, fieldName);
28
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
29
+ if (!emailRegex.test(email)) {
30
+ throw new ValidationError(`${fieldName} must be a valid email address`, fieldName);
31
+ }
32
+ return email;
33
+ }
34
+ static password(value, fieldName, minLength = 8) {
35
+ const password = this.nonEmptyString(value, fieldName);
36
+ if (password.length < minLength) {
37
+ throw new ValidationError(`${fieldName} must be at least ${minLength} characters long`, fieldName);
38
+ }
39
+ return password;
40
+ }
41
+ static positiveNumber(value, fieldName) {
42
+ this.required(value, fieldName);
43
+ const num = Number(value);
44
+ if (isNaN(num) || num <= 0) {
45
+ throw new ValidationError(`${fieldName} must be a positive number`, fieldName);
46
+ }
47
+ return num;
48
+ }
49
+ static range(value, fieldName, min, max) {
50
+ const num = this.positiveNumber(value, fieldName);
51
+ if (num < min || num > max) {
52
+ throw new ValidationError(`${fieldName} must be between ${min} and ${max}`, fieldName);
53
+ }
54
+ return num;
55
+ }
56
+ static oneOf(value, fieldName, allowedValues) {
57
+ this.required(value, fieldName);
58
+ if (!allowedValues.includes(value)) {
59
+ throw new ValidationError(`${fieldName} must be one of: ${allowedValues.join(", ")}`, fieldName);
60
+ }
61
+ return value;
62
+ }
63
+ static url(value, fieldName) {
64
+ const url = this.nonEmptyString(value, fieldName);
65
+ try {
66
+ new URL(url);
67
+ return url;
68
+ }
69
+ catch {
70
+ throw new ValidationError(`${fieldName} must be a valid URL`, fieldName);
71
+ }
72
+ }
73
+ static jwtToken(value, fieldName) {
74
+ const token = this.nonEmptyString(value, fieldName);
75
+ const parts = token.split(".");
76
+ if (parts.length !== 3) {
77
+ throw new ValidationError(`${fieldName} must be a valid JWT token`, fieldName);
78
+ }
79
+ return token;
80
+ }
81
+ static uuid(value, fieldName) {
82
+ const uuid = this.nonEmptyString(value, fieldName);
83
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
84
+ if (!uuidRegex.test(uuid)) {
85
+ throw new ValidationError(`${fieldName} must be a valid UUID`, fieldName);
86
+ }
87
+ return uuid;
88
+ }
89
+ static object(value, fieldName) {
90
+ this.required(value, fieldName);
91
+ if (typeof value !== "object" || Array.isArray(value)) {
92
+ throw new ValidationError(`${fieldName} must be an object`, fieldName);
93
+ }
94
+ return value;
95
+ }
96
+ static array(value, fieldName) {
97
+ this.required(value, fieldName);
98
+ if (!Array.isArray(value)) {
99
+ throw new ValidationError(`${fieldName} must be an array`, fieldName);
100
+ }
101
+ return value;
102
+ }
103
+ static function(value, fieldName) {
104
+ this.required(value, fieldName);
105
+ if (typeof value !== "function") {
106
+ throw new ValidationError(`${fieldName} must be a function`, fieldName);
107
+ }
108
+ return value;
109
+ }
110
+ static validateConfig(config, requiredFields, fieldName = "config") {
111
+ this.object(config, fieldName);
112
+ for (const field of requiredFields) {
113
+ this.required(config[field], `${fieldName}.${field}`);
114
+ }
115
+ }
116
+ static pattern(value, fieldName, regex, message) {
117
+ const str = this.nonEmptyString(value, fieldName);
118
+ if (!regex.test(str)) {
119
+ throw new ValidationError(message || `${fieldName} does not match required pattern`, fieldName);
120
+ }
121
+ return str;
122
+ }
123
+ static date(value, fieldName) {
124
+ this.required(value, fieldName);
125
+ const date = new Date(value);
126
+ if (isNaN(date.getTime())) {
127
+ throw new ValidationError(`${fieldName} must be a valid date`, fieldName);
128
+ }
129
+ return date;
130
+ }
131
+ static boolean(value, fieldName) {
132
+ this.required(value, fieldName);
133
+ if (typeof value !== "boolean") {
134
+ throw new ValidationError(`${fieldName} must be a boolean`, fieldName);
135
+ }
136
+ return value;
137
+ }
138
+ }
139
+ exports.ValidationUtils = ValidationUtils;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soapjs/soap-auth",
3
- "version": "0.3.3",
3
+ "version": "0.4.4",
4
4
  "description": "",
5
5
  "homepage": "https://docs.soapjs.com",
6
6
  "repository": "https://github.com/soapjs/soap-auth",
@@ -15,20 +15,21 @@
15
15
  "prepublish": "npm run clean && tsc --project tsconfig.build.json"
16
16
  },
17
17
  "devDependencies": {
18
- "@soapjs/soap": "^0.5.8",
18
+ "@soapjs/soap": "^0.12.0",
19
19
  "@types/jest": "^27.0.3",
20
20
  "jest": "^27.4.5",
21
21
  "ts-jest": "^27.1.3",
22
22
  "typescript": "^4.8.2"
23
23
  },
24
24
  "peerDependencies": {
25
- "@soapjs/soap": ">=0.5.8"
25
+ "@soapjs/soap": ">=0.12.0"
26
+ },
27
+ "engines": {
28
+ "node": ">=18.0.0"
26
29
  },
27
30
  "dependencies": {
28
- "axios": "^1.7.9",
29
- "bcrypt": "^5.1.1",
31
+ "bcrypt": "^6.0.0",
30
32
  "jsonwebtoken": "^9.0.2",
31
- "jwks-rsa": "^3.1.0",
32
- "uuid": "^9.0.1"
33
+ "jwks-rsa": "^3.1.0"
33
34
  }
34
35
  }
@@ -1 +0,0 @@
1
- export {};
@@ -1,42 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const globals_1 = require("@jest/globals");
4
- const soap_auth_1 = require("../soap-auth");
5
- describe("SoapAuth", () => {
6
- let soapAuth;
7
- const mockLogger = { error: globals_1.jest.fn(), info: globals_1.jest.fn() };
8
- const mockStrategy = {
9
- authenticate: globals_1.jest.fn(),
10
- init: globals_1.jest.fn(),
11
- logout: globals_1.jest.fn(),
12
- };
13
- beforeEach(() => {
14
- globals_1.jest.clearAllMocks();
15
- soapAuth = new soap_auth_1.SoapAuth({ logger: mockLogger });
16
- });
17
- test("addStrategy should add a valid strategy", () => {
18
- expect(() => soapAuth.addStrategy(mockStrategy, "jwt", "http")).not.toThrow();
19
- expect(soapAuth.hasStrategy("jwt", "http")).toBe(true);
20
- });
21
- test("addStrategy should throw error if strategy is invalid", () => {
22
- expect(() => soapAuth.addStrategy({}, "invalid", "http")).toThrow("Invalid authentication strategy: does not implement required methods.");
23
- });
24
- test("removeStrategy should remove an existing strategy", () => {
25
- soapAuth.addStrategy(mockStrategy, "jwt", "http");
26
- expect(soapAuth.hasStrategy("jwt", "http")).toBe(true);
27
- soapAuth.removeStrategy("jwt", "http");
28
- expect(soapAuth.hasStrategy("jwt", "http")).toBe(false);
29
- });
30
- test("getStrategy should return an existing strategy", () => {
31
- soapAuth.addStrategy(mockStrategy, "jwt", "http");
32
- expect(soapAuth.getStrategy("jwt", "http")).toBe(mockStrategy);
33
- });
34
- test("getStrategy should throw an error if strategy does not exist", () => {
35
- expect(() => soapAuth.getStrategy("nonexistent", "http")).toThrow('Authentication strategy "nonexistent" not found.');
36
- });
37
- test("listStrategies should return all registered strategy names", () => {
38
- soapAuth.addStrategy(mockStrategy, "jwt", "http");
39
- soapAuth.addStrategy(mockStrategy, "oauth", "http");
40
- expect(soapAuth.listStrategies("http")).toEqual(["jwt", "oauth"]);
41
- });
42
- });
@@ -1,55 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const errors_1 = require("../../errors");
4
- const account_lock_service_1 = require("../account-lock.service");
5
- const mockConfig = {
6
- isAccountLocked: jest.fn(),
7
- lockAccount: jest.fn(),
8
- hasAccountLockExpired: jest.fn(),
9
- removeAccountLock: jest.fn(),
10
- logFailedAttempt: jest.fn(),
11
- notifyOnLockout: jest.fn(),
12
- };
13
- const mockLogger = {
14
- error: jest.fn(),
15
- };
16
- describe("AccountLockService", () => {
17
- let service;
18
- const mockAccount = { id: "123" };
19
- beforeEach(() => {
20
- jest.clearAllMocks();
21
- service = new account_lock_service_1.AccountLockService(mockConfig, mockLogger);
22
- });
23
- it("isAccountLocked throws error if account is locked", async () => {
24
- mockConfig.isAccountLocked.mockResolvedValue(true);
25
- await expect(service.isAccountLocked(mockAccount)).rejects.toThrow(errors_1.AccountLockedError);
26
- });
27
- it("isAccountLocked returns false if account is not locked", async () => {
28
- mockConfig.isAccountLocked.mockResolvedValue(false);
29
- await expect(service.isAccountLocked(mockAccount)).resolves.toBe(false);
30
- });
31
- it("lockAccount locks the account and notifies if enabled", async () => {
32
- mockConfig.notifyOnLockout = jest.fn();
33
- await service.lockAccount(mockAccount);
34
- expect(mockConfig.lockAccount).toHaveBeenCalledWith(mockAccount);
35
- expect(mockConfig.notifyOnLockout).toHaveBeenCalledWith(mockAccount);
36
- });
37
- it("lockAccount does not notify if notifyOnLockout is not defined", async () => {
38
- mockConfig.notifyOnLockout = undefined;
39
- await service.lockAccount(mockAccount);
40
- expect(mockConfig.lockAccount).toHaveBeenCalledWith(mockAccount);
41
- });
42
- it("hasAccountLockExpired returns expected value", async () => {
43
- mockConfig.hasAccountLockExpired.mockResolvedValue(true);
44
- await expect(service.hasAccountLockExpired(mockAccount)).resolves.toBe(true);
45
- });
46
- it("removeAccountLock calls the correct method", async () => {
47
- await service.removeAccountLock(mockAccount);
48
- expect(mockConfig.removeAccountLock).toHaveBeenCalledWith(mockAccount);
49
- });
50
- it("logFailedAttempt logs action and handles error if logging fails", async () => {
51
- mockConfig.logFailedAttempt.mockRejectedValue(new Error("Logging failed"));
52
- await service.logFailedAttempt("LOGIN_ATTEMPT", mockAccount, {});
53
- expect(mockLogger.error).toHaveBeenCalled();
54
- });
55
- });