@tomei/sso 0.65.0 → 0.65.1

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/.gitlab-ci.yml CHANGED
@@ -1,7 +1,13 @@
1
1
  variables:
2
2
  SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
3
3
  GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
4
+
5
+ stages:
6
+ - build
7
+ - publish
8
+
4
9
  sonarcloud-check:
10
+ stage: build
5
11
  image:
6
12
  name: sonarsource/sonar-scanner-cli:latest
7
13
  entrypoint: [""]
@@ -14,3 +20,37 @@ sonarcloud-check:
14
20
  only:
15
21
  - merge_requests
16
22
  - main
23
+
24
+ package-build:
25
+ stage: build
26
+ image: node:20-alpine
27
+ tags:
28
+ - shared
29
+ script:
30
+ - npm ci
31
+ - npm run build
32
+ artifacts:
33
+ paths:
34
+ - dist/
35
+ expire_in: 1 hour
36
+ rules:
37
+ - if: '$CI_COMMIT_TAG =~ /^prod-v[0-9]+\.[0-9]+\.[0-9]+(-retry\.[0-9]+)?$/'
38
+
39
+ package-publish:
40
+ stage: publish
41
+ image: node:20-alpine
42
+ tags:
43
+ - shared
44
+ needs:
45
+ - job: package-build
46
+ artifacts: true
47
+ rules:
48
+ - if: '$CI_COMMIT_TAG =~ /^prod-v[0-9]+\.[0-9]+\.[0-9]+(-retry\.[0-9]+)?$/'
49
+ when: manual
50
+ allow_failure: false
51
+ script:
52
+ - npm ci
53
+ - test -n "$NPM_TOKEN"
54
+ - printf '%s\n' "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
55
+ - npm whoami --registry https://registry.npmjs.org/
56
+ - npm publish --tag latest --access public
package/.husky/commit-msg CHANGED
File without changes
package/.husky/pre-commit CHANGED
File without changes
@@ -0,0 +1,87 @@
1
+ import { User } from '../../../../src/components/login-user/user';
2
+ import { UserPasswordHistory } from '../../../../src/components/user-password-history/user-password-history';
3
+ import { YN } from '../../../../src/enum/yn.enum';
4
+
5
+ // User and LoginUser import each other; this test exercises only User's expiry policy.
6
+ jest.mock('../../../../src/components/login-user/login-user', () => ({
7
+ LoginUser: class {},
8
+ }));
9
+
10
+ describe('User.getPasswordExpiryStatus', () => {
11
+ const originalExpiryMonths = process.env.PASSWORD_EXPIRY_MONTHS;
12
+ const originalWarningDays = process.env.PASSWORD_EXPIRY_WARNING_DAYS;
13
+
14
+ const user = (password: string | null) =>
15
+ Object.assign(Object.create(User.prototype), {
16
+ ObjectId: '123',
17
+ _Password: password,
18
+ _NeedToChangePasswordYN: YN.No,
19
+ }) as User;
20
+
21
+ beforeEach(() => {
22
+ process.env.PASSWORD_EXPIRY_MONTHS = '1';
23
+ process.env.PASSWORD_EXPIRY_WARNING_DAYS = '14';
24
+ jest.useFakeTimers().setSystemTime(new Date('2026-02-20T12:00:00Z'));
25
+ });
26
+
27
+ afterEach(() => {
28
+ jest.restoreAllMocks();
29
+ jest.useRealTimers();
30
+ if (originalExpiryMonths === undefined)
31
+ delete process.env.PASSWORD_EXPIRY_MONTHS;
32
+ else process.env.PASSWORD_EXPIRY_MONTHS = originalExpiryMonths;
33
+ if (originalWarningDays === undefined)
34
+ delete process.env.PASSWORD_EXPIRY_WARNING_DAYS;
35
+ else process.env.PASSWORD_EXPIRY_WARNING_DAYS = originalWarningDays;
36
+ });
37
+
38
+ it('disables expiry when the period is zero', async () => {
39
+ process.env.PASSWORD_EXPIRY_MONTHS = '0';
40
+ const lookup = jest.spyOn(UserPasswordHistory, 'getLatestByUserId');
41
+
42
+ expect(await user('hash').getPasswordExpiryStatus()).toEqual({
43
+ expired: false,
44
+ expiringSoon: false,
45
+ expiresInDays: null,
46
+ });
47
+ expect(lookup).not.toHaveBeenCalled();
48
+ });
49
+
50
+ it('skips an account without a local password', async () => {
51
+ const lookup = jest.spyOn(UserPasswordHistory, 'getLatestByUserId');
52
+
53
+ expect(await user(null).getPasswordExpiryStatus()).toEqual({
54
+ expired: false,
55
+ expiringSoon: false,
56
+ expiresInDays: null,
57
+ });
58
+ expect(lookup).not.toHaveBeenCalled();
59
+ });
60
+
61
+ it('expires an account without password history', async () => {
62
+ jest
63
+ .spyOn(UserPasswordHistory, 'getLatestByUserId')
64
+ .mockResolvedValue(null);
65
+
66
+ expect(await user('hash').getPasswordExpiryStatus()).toEqual({
67
+ expired: true,
68
+ expiringSoon: false,
69
+ expiresInDays: null,
70
+ });
71
+ });
72
+
73
+ it('warns before expiry and clamps month-end dates', async () => {
74
+ jest.spyOn(UserPasswordHistory, 'getLatestByUserId').mockResolvedValue({
75
+ CreatedAt: new Date('2026-01-31T12:00:00Z'),
76
+ } as UserPasswordHistory);
77
+
78
+ expect(await user('hash').getPasswordExpiryStatus()).toEqual({
79
+ expired: false,
80
+ expiringSoon: true,
81
+ expiresInDays: 8,
82
+ });
83
+
84
+ jest.setSystemTime(new Date('2026-02-28T12:00:00Z'));
85
+ expect((await user('hash').getPasswordExpiryStatus()).expired).toBe(true);
86
+ });
87
+ });
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const user_1 = require("../../../../src/components/login-user/user");
13
+ const user_password_history_1 = require("../../../../src/components/user-password-history/user-password-history");
14
+ const yn_enum_1 = require("../../../../src/enum/yn.enum");
15
+ jest.mock('../../../../src/components/login-user/login-user', () => ({
16
+ LoginUser: class {
17
+ },
18
+ }));
19
+ describe('User.getPasswordExpiryStatus', () => {
20
+ const originalExpiryMonths = process.env.PASSWORD_EXPIRY_MONTHS;
21
+ const originalWarningDays = process.env.PASSWORD_EXPIRY_WARNING_DAYS;
22
+ const user = (password) => Object.assign(Object.create(user_1.User.prototype), {
23
+ ObjectId: '123',
24
+ _Password: password,
25
+ _NeedToChangePasswordYN: yn_enum_1.YN.No,
26
+ });
27
+ beforeEach(() => {
28
+ process.env.PASSWORD_EXPIRY_MONTHS = '1';
29
+ process.env.PASSWORD_EXPIRY_WARNING_DAYS = '14';
30
+ jest.useFakeTimers().setSystemTime(new Date('2026-02-20T12:00:00Z'));
31
+ });
32
+ afterEach(() => {
33
+ jest.restoreAllMocks();
34
+ jest.useRealTimers();
35
+ if (originalExpiryMonths === undefined)
36
+ delete process.env.PASSWORD_EXPIRY_MONTHS;
37
+ else
38
+ process.env.PASSWORD_EXPIRY_MONTHS = originalExpiryMonths;
39
+ if (originalWarningDays === undefined)
40
+ delete process.env.PASSWORD_EXPIRY_WARNING_DAYS;
41
+ else
42
+ process.env.PASSWORD_EXPIRY_WARNING_DAYS = originalWarningDays;
43
+ });
44
+ it('disables expiry when the period is zero', () => __awaiter(void 0, void 0, void 0, function* () {
45
+ process.env.PASSWORD_EXPIRY_MONTHS = '0';
46
+ const lookup = jest.spyOn(user_password_history_1.UserPasswordHistory, 'getLatestByUserId');
47
+ expect(yield user('hash').getPasswordExpiryStatus()).toEqual({
48
+ expired: false,
49
+ expiringSoon: false,
50
+ expiresInDays: null,
51
+ });
52
+ expect(lookup).not.toHaveBeenCalled();
53
+ }));
54
+ it('skips an account without a local password', () => __awaiter(void 0, void 0, void 0, function* () {
55
+ const lookup = jest.spyOn(user_password_history_1.UserPasswordHistory, 'getLatestByUserId');
56
+ expect(yield user(null).getPasswordExpiryStatus()).toEqual({
57
+ expired: false,
58
+ expiringSoon: false,
59
+ expiresInDays: null,
60
+ });
61
+ expect(lookup).not.toHaveBeenCalled();
62
+ }));
63
+ it('expires an account without password history', () => __awaiter(void 0, void 0, void 0, function* () {
64
+ jest
65
+ .spyOn(user_password_history_1.UserPasswordHistory, 'getLatestByUserId')
66
+ .mockResolvedValue(null);
67
+ expect(yield user('hash').getPasswordExpiryStatus()).toEqual({
68
+ expired: true,
69
+ expiringSoon: false,
70
+ expiresInDays: null,
71
+ });
72
+ }));
73
+ it('warns before expiry and clamps month-end dates', () => __awaiter(void 0, void 0, void 0, function* () {
74
+ jest.spyOn(user_password_history_1.UserPasswordHistory, 'getLatestByUserId').mockResolvedValue({
75
+ CreatedAt: new Date('2026-01-31T12:00:00Z'),
76
+ });
77
+ expect(yield user('hash').getPasswordExpiryStatus()).toEqual({
78
+ expired: false,
79
+ expiringSoon: true,
80
+ expiresInDays: 8,
81
+ });
82
+ jest.setSystemTime(new Date('2026-02-28T12:00:00Z'));
83
+ expect((yield user('hash').getPasswordExpiryStatus()).expired).toBe(true);
84
+ }));
85
+ });
86
+ //# sourceMappingURL=password-expiry.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-expiry.spec.js","sourceRoot":"","sources":["../../../../../__tests__/unit/components/login-user/password-expiry.spec.ts"],"names":[],"mappings":"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"}
@@ -176,6 +176,11 @@ export declare class User extends UserBase {
176
176
  static findByEmail(AuthContext: AuthContext, dbTransaction: any, Email: string): Promise<User>;
177
177
  updatePasscode(loginUser: User, passcode: string, dbTransaction: any): Promise<void>;
178
178
  updatePassword(loginUser: User, oldPassword: string, newPassword: string, dbTransaction: any): Promise<void>;
179
+ getPasswordExpiryStatus(dbTransaction?: any): Promise<{
180
+ expired: boolean;
181
+ expiringSoon: boolean;
182
+ expiresInDays: number | null;
183
+ }>;
179
184
  verifyPasscode(passcode: string): Promise<void>;
180
185
  getReportingUser(dbTransaction: any): Promise<{
181
186
  Email: string;
@@ -2042,6 +2042,7 @@ class User extends general_1.UserBase {
2042
2042
  };
2043
2043
  this.Password = passwordHash;
2044
2044
  this.LastPasswordChangedAt = new Date();
2045
+ this.NeedToChangePasswordYN = yn_enum_1.YN.No;
2045
2046
  const entityValueAfter = {
2046
2047
  UserId: this.UserId,
2047
2048
  UserName: this.UserName,
@@ -2073,6 +2074,7 @@ class User extends general_1.UserBase {
2073
2074
  yield User._Repository.update({
2074
2075
  Password: this.Password,
2075
2076
  LastPasswordChangedAt: this.LastPasswordChangedAt,
2077
+ NeedToChangePasswordYN: this.NeedToChangePasswordYN,
2076
2078
  }, {
2077
2079
  where: {
2078
2080
  UserId: this.UserId,
@@ -2095,6 +2097,48 @@ class User extends general_1.UserBase {
2095
2097
  }
2096
2098
  });
2097
2099
  }
2100
+ getPasswordExpiryStatus(dbTransaction) {
2101
+ return __awaiter(this, void 0, void 0, function* () {
2102
+ try {
2103
+ const expiryMonthsRaw = Number(process.env.PASSWORD_EXPIRY_MONTHS);
2104
+ const expiryMonths = Number.isFinite(expiryMonthsRaw)
2105
+ ? Math.max(0, Math.floor(expiryMonthsRaw))
2106
+ : 0;
2107
+ if (expiryMonths === 0) {
2108
+ return { expired: false, expiringSoon: false, expiresInDays: null };
2109
+ }
2110
+ if (!this.Password) {
2111
+ return { expired: false, expiringSoon: false, expiresInDays: null };
2112
+ }
2113
+ if (this.NeedToChangePasswordYN === yn_enum_1.YN.Yes) {
2114
+ return { expired: true, expiringSoon: false, expiresInDays: null };
2115
+ }
2116
+ const warningDaysRaw = Number(process.env.PASSWORD_EXPIRY_WARNING_DAYS);
2117
+ const warningDays = Number.isFinite(warningDaysRaw)
2118
+ ? Math.max(0, Math.floor(warningDaysRaw))
2119
+ : 14;
2120
+ const latestHistory = yield user_password_history_1.UserPasswordHistory.getLatestByUserId(dbTransaction, this.UserId);
2121
+ if (!latestHistory) {
2122
+ return { expired: true, expiringSoon: false, expiresInDays: null };
2123
+ }
2124
+ const createdAt = latestHistory.CreatedAt;
2125
+ const expiryMonth = createdAt.getUTCMonth() + expiryMonths;
2126
+ const lastDayOfExpiryMonth = new Date(Date.UTC(createdAt.getUTCFullYear(), expiryMonth + 1, 0)).getUTCDate();
2127
+ const expiryDate = new Date(Date.UTC(createdAt.getUTCFullYear(), expiryMonth, Math.min(createdAt.getUTCDate(), lastDayOfExpiryMonth), createdAt.getUTCHours(), createdAt.getUTCMinutes(), createdAt.getUTCSeconds(), createdAt.getUTCMilliseconds()));
2128
+ const now = new Date();
2129
+ if (now.getTime() >= expiryDate.getTime()) {
2130
+ return { expired: true, expiringSoon: false, expiresInDays: null };
2131
+ }
2132
+ const msPerDay = 24 * 60 * 60 * 1000;
2133
+ const expiringSoon = now.getTime() >= expiryDate.getTime() - warningDays * msPerDay;
2134
+ const expiresInDays = Math.ceil((expiryDate.getTime() - now.getTime()) / msPerDay);
2135
+ return { expired: false, expiringSoon, expiresInDays };
2136
+ }
2137
+ catch (error) {
2138
+ throw error;
2139
+ }
2140
+ });
2141
+ }
2098
2142
  verifyPasscode(passcode) {
2099
2143
  return __awaiter(this, void 0, void 0, function* () {
2100
2144
  try {