@loomcore/api 0.2.18 → 0.2.20

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.
@@ -1,11 +1,21 @@
1
+ import { type IModelSpec } from "@loomcore/common/models";
1
2
  import type { Application, Request, Response } from "express";
2
3
  import type { IDatabase } from "../databases/models/index.js";
3
4
  import { OrganizationService, UserService } from "../services/index.js";
5
+ export interface AuthControllerOptions {
6
+ userService?: UserService;
7
+ userSpec?: IModelSpec;
8
+ publicUserSpec?: IModelSpec;
9
+ }
4
10
  export declare class AuthController {
5
11
  database: IDatabase;
6
12
  userService: UserService;
7
13
  organizationService: OrganizationService;
8
- constructor(app: Application, database: IDatabase);
14
+ userSpec: IModelSpec;
15
+ publicUserSpec: IModelSpec;
16
+ publicUserContextSpec: IModelSpec;
17
+ loginResponseSpec: IModelSpec;
18
+ constructor(app: Application, database: IDatabase, options?: AuthControllerOptions);
9
19
  mapRoutes(app: Application): void;
10
20
  login(req: Request, res: Response): Promise<void>;
11
21
  requestTokenUsingRefreshToken(req: Request, res: Response): Promise<void>;
@@ -1,19 +1,35 @@
1
- import { EmptyUserContext, LoginResponseSpec, PublicUserContextSpec, passwordValidator, TokenResponseSpec, UserSpec, } from "@loomcore/common/models";
1
+ import { EmptyUserContext, passwordValidator, TokenResponseSpec, } from "@loomcore/common/models";
2
2
  import { entityUtils } from "@loomcore/common/utils";
3
3
  import { config } from "../config/base-api-config.js";
4
4
  import { BadRequestError, UnauthenticatedError } from "../errors/index.js";
5
5
  import { isAuthorized } from "../middleware/index.js";
6
6
  import { OrganizationService, UserService } from "../services/index.js";
7
- import { attemptLogin, changePassword, getAndSetDeviceIdCookie, getDeviceIdFromCookie, requestTokenUsingRefreshToken, resetPassword, sendResetPasswordEmail, } from "../utils/auth/index.js";
7
+ import { attemptLogin, changePassword, createUserContextSpec, getAndSetDeviceIdCookie, getDeviceIdFromCookie, requestTokenUsingRefreshToken, resetPassword, resolveAuthUserSpecs, sendResetPasswordEmail, setAuthUserContextSpec, } from "../utils/auth/index.js";
8
8
  import { apiUtils } from "../utils/index.js";
9
9
  export class AuthController {
10
10
  database;
11
11
  userService;
12
12
  organizationService;
13
- constructor(app, database) {
13
+ userSpec;
14
+ publicUserSpec;
15
+ publicUserContextSpec;
16
+ loginResponseSpec;
17
+ constructor(app, database, options = {}) {
14
18
  this.database = database;
15
- this.userService = new UserService(database);
19
+ const resolved = resolveAuthUserSpecs({
20
+ userSpec: options.userSpec,
21
+ publicUserSpec: options.publicUserSpec,
22
+ });
23
+ this.userSpec = resolved.userSpec;
24
+ this.publicUserSpec = resolved.publicUserSpec;
25
+ this.publicUserContextSpec = resolved.publicUserContextSpec;
26
+ this.loginResponseSpec = resolved.loginResponseSpec;
27
+ this.userService =
28
+ options.userService ?? new UserService(database, this.userSpec);
16
29
  this.organizationService = new OrganizationService(database);
30
+ if (options.userSpec) {
31
+ setAuthUserContextSpec(createUserContextSpec(this.userSpec));
32
+ }
17
33
  this.mapRoutes(app);
18
34
  }
19
35
  mapRoutes(app) {
@@ -45,8 +61,8 @@ export class AuthController {
45
61
  }
46
62
  res.set("Content-Type", "application/json");
47
63
  const deviceId = getAndSetDeviceIdCookie(req, res);
48
- const loginResponse = await attemptLogin(this.database, email, password, deviceId, organization);
49
- apiUtils.apiResponse(res, 200, { data: loginResponse }, LoginResponseSpec);
64
+ const loginResponse = await attemptLogin(this.database, email, password, deviceId, organization, this.userService);
65
+ apiUtils.apiResponse(res, 200, { data: loginResponse }, this.loginResponseSpec);
50
66
  }
51
67
  async requestTokenUsingRefreshToken(req, res) {
52
68
  const userContext = req.userContext;
@@ -66,7 +82,7 @@ export class AuthController {
66
82
  }
67
83
  async getUserContext(req, res) {
68
84
  const userContext = req.userContext;
69
- apiUtils.apiResponse(res, 200, { data: userContext }, PublicUserContextSpec);
85
+ apiUtils.apiResponse(res, 200, { data: userContext }, this.publicUserContextSpec);
70
86
  }
71
87
  afterAuth(_req, _res, _loginResponse) {
72
88
  console.log("in afterAuth");
@@ -77,9 +93,9 @@ export class AuthController {
77
93
  throw new BadRequestError("Missing required fields: userContext is required.");
78
94
  }
79
95
  const password = req.body?.password;
80
- const validationErrors = entityUtils.validate(UserSpec, { password: password }, true, passwordValidator);
96
+ const validationErrors = entityUtils.validate(this.userSpec, { password: password }, true, passwordValidator);
81
97
  entityUtils.handleValidationResult(validationErrors, "AuthController.changePassword");
82
- const updateResult = await changePassword(this.database, userContext, password);
98
+ const updateResult = await changePassword(this.database, userContext, password, this.userService);
83
99
  apiUtils.apiResponse(res, 200, { data: updateResult });
84
100
  }
85
101
  async forgotPassword(req, res) {
@@ -127,7 +143,7 @@ export class AuthController {
127
143
  throw new BadRequestError("Missing required fields: organization is required.");
128
144
  }
129
145
  }
130
- const response = await resetPassword(this.database, email, token, password, organization);
146
+ const response = await resetPassword(this.database, email, token, password, organization, this.userService, this.userSpec);
131
147
  apiUtils.apiResponse(res, 200, { data: response });
132
148
  }
133
149
  }
@@ -1,9 +1,14 @@
1
- import { type IUser } from "@loomcore/common/models";
1
+ import { type IModelSpec, type IUser } from "@loomcore/common/models";
2
2
  import type { Application } from "express";
3
3
  import type { IDatabase } from "../databases/models/index.js";
4
4
  import { UserService } from "../services/index.js";
5
5
  import { ApiController } from "./api.controller.js";
6
+ export interface UsersControllerOptions {
7
+ userService?: UserService;
8
+ userSpec?: IModelSpec;
9
+ publicUserSpec?: IModelSpec;
10
+ }
6
11
  export declare class UsersController extends ApiController<IUser> {
7
12
  userService: UserService;
8
- constructor(app: Application, database: IDatabase);
13
+ constructor(app: Application, database: IDatabase, options?: UsersControllerOptions);
9
14
  }
@@ -1,11 +1,13 @@
1
- import { PublicUserSpec, UserSpec } from "@loomcore/common/models";
1
+ import { PublicUserSpec, UserSpec, } from "@loomcore/common/models";
2
2
  import { UserService } from "../services/index.js";
3
3
  import { ApiController } from "./api.controller.js";
4
4
  export class UsersController extends ApiController {
5
5
  userService;
6
- constructor(app, database) {
7
- const userService = new UserService(database);
8
- super("users", app, userService, "user", UserSpec, PublicUserSpec);
6
+ constructor(app, database, options = {}) {
7
+ const userSpec = options.userSpec ?? UserSpec;
8
+ const publicUserSpec = options.publicUserSpec ?? PublicUserSpec;
9
+ const userService = options.userService ?? new UserService(database, userSpec);
10
+ super("users", app, userService, "user", userSpec, publicUserSpec);
9
11
  this.userService = userService;
10
12
  }
11
13
  }
@@ -1,5 +1,9 @@
1
+ import { LeftJoin } from "../../operations/left-join.operation.js";
2
+ import { InnerJoin } from "../../operations/inner-join.operation.js";
3
+ import { LeftJoinMany } from "../../operations/left-join-many.operation.js";
1
4
  import { BadRequestError, IdNotFoundError } from "../../../errors/index.js";
2
5
  import { buildJoinClauses } from '../utils/build-join-clauses.js';
6
+ import { buildSelectClause } from '../utils/build-select-clause.js';
3
7
  export async function fullUpdateById(client, operations, id, entity, pluralResourceName) {
4
8
  try {
5
9
  const tableColumns = await client.query(`
@@ -49,16 +53,25 @@ export async function fullUpdateById(client, operations, id, entity, pluralResou
49
53
  if (result.rowCount === 0) {
50
54
  throw new IdNotFoundError();
51
55
  }
52
- const joinClauses = buildJoinClauses(operations, pluralResourceName);
56
+ const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
57
+ const joinClauses = hasJoins
58
+ ? buildJoinClauses(operations, pluralResourceName, { oneToOneOnly: true })
59
+ : buildJoinClauses(operations, pluralResourceName);
60
+ const selectClause = hasJoins
61
+ ? await buildSelectClause(client, pluralResourceName, operations)
62
+ : '*';
63
+ const idColumn = hasJoins ? `"${pluralResourceName}"."_id"` : '"_id"';
53
64
  const selectQuery = `
54
- SELECT * FROM "${pluralResourceName}" ${joinClauses}
55
- WHERE "_id" = $1 LIMIT 1
65
+ SELECT ${selectClause} FROM "${pluralResourceName}" ${joinClauses}
66
+ WHERE ${idColumn} = $1 LIMIT 1
56
67
  `;
57
68
  const selectResult = await client.query(selectQuery, [id]);
58
69
  if (selectResult.rows.length === 0) {
59
70
  throw new IdNotFoundError();
60
71
  }
61
- return selectResult.rows[0];
72
+ return hasJoins
73
+ ? selectResult.rows[0].entity
74
+ : selectResult.rows[0];
62
75
  }
63
76
  catch (err) {
64
77
  if (err instanceof IdNotFoundError) {
@@ -1,5 +1,9 @@
1
+ import { LeftJoin } from "../../operations/left-join.operation.js";
2
+ import { InnerJoin } from "../../operations/inner-join.operation.js";
3
+ import { LeftJoinMany } from "../../operations/left-join-many.operation.js";
1
4
  import { BadRequestError, IdNotFoundError } from "../../../errors/index.js";
2
5
  import { buildJoinClauses } from '../utils/build-join-clauses.js';
6
+ import { buildSelectClause } from '../utils/build-select-clause.js';
3
7
  import { columnsAndValuesFromEntity } from '../utils/columns-and-values-from-entity.js';
4
8
  export async function partialUpdateById(client, operations, id, entity, pluralResourceName) {
5
9
  try {
@@ -19,16 +23,25 @@ export async function partialUpdateById(client, operations, id, entity, pluralRe
19
23
  if (result.rowCount === 0) {
20
24
  throw new IdNotFoundError();
21
25
  }
22
- const joinClauses = buildJoinClauses(operations, pluralResourceName);
26
+ const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
27
+ const joinClauses = hasJoins
28
+ ? buildJoinClauses(operations, pluralResourceName, { oneToOneOnly: true })
29
+ : buildJoinClauses(operations, pluralResourceName);
30
+ const selectClause = hasJoins
31
+ ? await buildSelectClause(client, pluralResourceName, operations)
32
+ : '*';
33
+ const idColumn = hasJoins ? `"${pluralResourceName}"."_id"` : '"_id"';
23
34
  const selectQuery = `
24
- SELECT * FROM "${pluralResourceName}" ${joinClauses}
25
- WHERE "_id" = $1 LIMIT 1
35
+ SELECT ${selectClause} FROM "${pluralResourceName}" ${joinClauses}
36
+ WHERE ${idColumn} = $1 LIMIT 1
26
37
  `;
27
38
  const selectResult = await client.query(selectQuery, [id]);
28
39
  if (selectResult.rows.length === 0) {
29
40
  throw new IdNotFoundError();
30
41
  }
31
- return selectResult.rows[0];
42
+ return hasJoins
43
+ ? selectResult.rows[0].entity
44
+ : selectResult.rows[0];
32
45
  }
33
46
  catch (err) {
34
47
  if (err instanceof IdNotFoundError) {
@@ -1,7 +1,11 @@
1
+ import { LeftJoin } from "../../operations/left-join.operation.js";
2
+ import { InnerJoin } from "../../operations/inner-join.operation.js";
3
+ import { LeftJoinMany } from "../../operations/left-join-many.operation.js";
1
4
  import { BadRequestError, NotFoundError } from "../../../errors/index.js";
2
5
  import { buildWhereClause } from '../utils/build-where-clause.js';
3
6
  import { buildJoinClauses } from '../utils/build-join-clauses.js';
4
7
  import { buildOrderByClause } from '../utils/build-order-by-clause.js';
8
+ import { buildSelectClause } from '../utils/build-select-clause.js';
5
9
  import { columnsAndValuesFromEntity } from '../utils/columns-and-values-from-entity.js';
6
10
  export async function update(client, queryObject, entity, operations, pluralResourceName) {
7
11
  try {
@@ -27,14 +31,24 @@ export async function update(client, queryObject, entity, operations, pluralReso
27
31
  if (result.rowCount === 0) {
28
32
  throw new NotFoundError('No records found matching update query');
29
33
  }
30
- const joinClauses = buildJoinClauses(operations, pluralResourceName);
31
- const orderByClause = buildOrderByClause(queryObject);
34
+ const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
35
+ const joinClauses = hasJoins
36
+ ? buildJoinClauses(operations, pluralResourceName, { oneToOneOnly: true })
37
+ : buildJoinClauses(operations, pluralResourceName);
38
+ const orderByClause = buildOrderByClause(queryObject, hasJoins ? { tablePrefix: pluralResourceName } : undefined);
39
+ const tablePrefix = hasJoins ? pluralResourceName : undefined;
40
+ const { whereClause: selectWhereClause, values: selectWhereValues } = buildWhereClause(queryObject, [], tablePrefix);
41
+ const selectClause = hasJoins
42
+ ? await buildSelectClause(client, pluralResourceName, operations)
43
+ : '*';
32
44
  const selectQuery = `
33
- SELECT * FROM "${pluralResourceName}" ${joinClauses}
34
- ${whereClause} ${orderByClause}
45
+ SELECT ${selectClause} FROM "${pluralResourceName}" ${joinClauses}
46
+ ${selectWhereClause} ${orderByClause}
35
47
  `.trim();
36
- const selectResult = await client.query(selectQuery, whereValues);
37
- return selectResult.rows;
48
+ const selectResult = await client.query(selectQuery, selectWhereValues);
49
+ return hasJoins
50
+ ? selectResult.rows.map(r => r.entity)
51
+ : selectResult.rows;
38
52
  }
39
53
  catch (err) {
40
54
  if (err instanceof NotFoundError) {
@@ -1,6 +1,6 @@
1
- import { UserContextSpec } from "@loomcore/common/models";
2
1
  import jwt from "jsonwebtoken";
3
2
  import { UnauthenticatedError, UnauthorizedError } from "../errors/index.js";
3
+ import { getAuthUserContextSpec } from "../utils/auth/auth-user-specs.util.js";
4
4
  import { getAuthConfig } from "../utils/auth/get-auth-config.util.js";
5
5
  const isAuthorized = (allowedFeatures) => {
6
6
  return (req, _res, next) => {
@@ -18,7 +18,7 @@ const isAuthorized = (allowedFeatures) => {
18
18
  const authConfig = getAuthConfig();
19
19
  try {
20
20
  const rawPayload = jwt.verify(token, authConfig.clientSecret);
21
- const userContext = UserContextSpec.decode(rawPayload);
21
+ const userContext = getAuthUserContextSpec().decode(rawPayload);
22
22
  req.userContext = userContext;
23
23
  if (userContext.authorizations.some((authorization) => authorization.feature === "admin")) {
24
24
  next();
@@ -1,9 +1,9 @@
1
- import { type IQueryOptions, type IUser, type IUserContext } from "@loomcore/common/models";
1
+ import { type IModelSpec, type IQueryOptions, type IUser, type IUserContext } from "@loomcore/common/models";
2
2
  import type { AppIdType } from "@loomcore/common/types";
3
3
  import type { IDatabase } from "../databases/models/index.js";
4
4
  import { MultiTenantApiService } from "./multi-tenant-api.service.js";
5
5
  export declare class UserService extends MultiTenantApiService<IUser> {
6
- constructor(database: IDatabase);
6
+ constructor(database: IDatabase, modelSpec?: IModelSpec);
7
7
  fullUpdateById(_userContext: IUserContext, _id: AppIdType, _entity: IUser): Promise<IUser>;
8
8
  update(userContext: IUserContext, queryObject: IQueryOptions, entity: Partial<IUser>): Promise<IUser[]>;
9
9
  batchUpdate(userContext: IUserContext, entities: Partial<IUser>[]): Promise<IUser[]>;
@@ -3,8 +3,8 @@ import { BadRequestError, ServerError } from "../errors/index.js";
3
3
  import { passwordUtils } from "../utils/password.utils.js";
4
4
  import { MultiTenantApiService } from "./multi-tenant-api.service.js";
5
5
  export class UserService extends MultiTenantApiService {
6
- constructor(database) {
7
- super(database, "users", "user", UserSpec);
6
+ constructor(database, modelSpec = UserSpec) {
7
+ super(database, "users", "user", modelSpec);
8
8
  }
9
9
  async fullUpdateById(_userContext, _id, _entity) {
10
10
  throw new ServerError("User full update is not allowed.");
@@ -1,3 +1,4 @@
1
1
  import { type ILoginResponse, type IOrganization } from "@loomcore/common/models";
2
2
  import type { IDatabase } from "../../databases/models/index.js";
3
- export declare function attemptLogin(database: IDatabase, email: string, password: string, deviceId: string, organization: IOrganization | null): Promise<ILoginResponse | null>;
3
+ import { UserService } from "../../services/user.service.js";
4
+ export declare function attemptLogin(database: IDatabase, email: string, password: string, deviceId: string, organization: IOrganization | null, userService?: UserService): Promise<ILoginResponse | null>;
@@ -4,13 +4,12 @@ import { UserService } from "../../services/user.service.js";
4
4
  import { getUserContextAuthorizations } from "../../services/utils/getUserContextAuthorizations.util.js";
5
5
  import { passwordUtils } from "../password.utils.js";
6
6
  import { logUserIn } from "./log-user-in.util.js";
7
- export async function attemptLogin(database, email, password, deviceId, organization) {
7
+ export async function attemptLogin(database, email, password, deviceId, organization, userService = new UserService(database)) {
8
8
  const lowerCaseEmail = email.toLowerCase();
9
9
  const userContext = {
10
10
  ...EmptyUserContext,
11
11
  organization: organization ?? undefined,
12
12
  };
13
- const userService = new UserService(database);
14
13
  const user = await userService.findOne(userContext, {
15
14
  filters: {
16
15
  email: { eq: lowerCaseEmail },
@@ -29,7 +28,7 @@ export async function attemptLogin(database, email, password, deviceId, organiza
29
28
  organization: organization ?? undefined,
30
29
  authorizations: authorizations,
31
30
  };
32
- const tokens = await logUserIn(database, authenticatedUserContext, deviceId);
31
+ const tokens = await logUserIn(database, authenticatedUserContext, deviceId, userService);
33
32
  return {
34
33
  tokens,
35
34
  userContext: authenticatedUserContext,
@@ -0,0 +1,15 @@
1
+ import { type IModelSpec } from "@loomcore/common/models";
2
+ export declare function createUserContextSpec(userModelSpec: IModelSpec): IModelSpec;
3
+ export declare function createLoginResponseSpec(publicUserContextSpec: IModelSpec): IModelSpec;
4
+ export declare function setAuthUserContextSpec(spec: IModelSpec): void;
5
+ export declare function getAuthUserContextSpec(): IModelSpec;
6
+ export declare function resetAuthUserContextSpec(): void;
7
+ export declare function resolveAuthUserSpecs(options: {
8
+ userSpec?: IModelSpec;
9
+ publicUserSpec?: IModelSpec;
10
+ }): {
11
+ userSpec: IModelSpec;
12
+ publicUserSpec: IModelSpec;
13
+ publicUserContextSpec: IModelSpec;
14
+ loginResponseSpec: IModelSpec;
15
+ };
@@ -0,0 +1,38 @@
1
+ import { OrganizationSpec, PublicUserSpec, TokenResponseSchema, UserContextAuthorizationSpec, UserContextSpec, UserSpec, } from "@loomcore/common/models";
2
+ import { entityUtils } from "@loomcore/common/utils";
3
+ import { Type } from "@sinclair/typebox";
4
+ export function createUserContextSpec(userModelSpec) {
5
+ return entityUtils.getModelSpec(Type.Object({
6
+ user: userModelSpec.fullSchema,
7
+ authorizations: Type.Array(UserContextAuthorizationSpec.fullSchema),
8
+ organization: OrganizationSpec.fullSchema,
9
+ }), { isEntity: false });
10
+ }
11
+ export function createLoginResponseSpec(publicUserContextSpec) {
12
+ return entityUtils.getModelSpec(Type.Object({
13
+ tokens: TokenResponseSchema,
14
+ userContext: publicUserContextSpec.fullSchema,
15
+ }), { isEntity: false });
16
+ }
17
+ let authUserContextSpec = UserContextSpec;
18
+ export function setAuthUserContextSpec(spec) {
19
+ authUserContextSpec = spec;
20
+ }
21
+ export function getAuthUserContextSpec() {
22
+ return authUserContextSpec;
23
+ }
24
+ export function resetAuthUserContextSpec() {
25
+ authUserContextSpec = UserContextSpec;
26
+ }
27
+ export function resolveAuthUserSpecs(options) {
28
+ const userSpec = options.userSpec ?? UserSpec;
29
+ const publicUserSpec = options.publicUserSpec ?? PublicUserSpec;
30
+ const publicUserContextSpec = createUserContextSpec(publicUserSpec);
31
+ const loginResponseSpec = createLoginResponseSpec(publicUserContextSpec);
32
+ return {
33
+ userSpec,
34
+ publicUserSpec,
35
+ publicUserContextSpec,
36
+ loginResponseSpec,
37
+ };
38
+ }
@@ -1,4 +1,5 @@
1
1
  import type { IUserContext } from "@loomcore/common/models";
2
2
  import type { IDatabase } from "../../databases/models/index.js";
3
3
  import type { UpdateResult } from "../../databases/models/update-result.js";
4
- export declare function changePassword(database: IDatabase, userContext: IUserContext, password: string): Promise<UpdateResult>;
4
+ import { UserService } from "../../services/user.service.js";
5
+ export declare function changePassword(database: IDatabase, userContext: IUserContext, password: string, userService?: UserService): Promise<UpdateResult>;
@@ -1,7 +1,6 @@
1
1
  import moment from "moment";
2
2
  import { UserService } from "../../services/user.service.js";
3
- export async function changePassword(database, userContext, password) {
4
- const userService = new UserService(database);
3
+ export async function changePassword(database, userContext, password, userService = new UserService(database)) {
5
4
  const updates = {
6
5
  password: password,
7
6
  _lastPasswordChange: moment().utc().toDate(),
@@ -1,4 +1,5 @@
1
1
  export * from "./attempt-login.util.js";
2
+ export * from "./auth-user-specs.util.js";
2
3
  export * from "./change-password.util.js";
3
4
  export * from "./create-new-refresh-token.util.js";
4
5
  export * from "./create-new-tokens.util.js";
@@ -1,4 +1,5 @@
1
1
  export * from "./attempt-login.util.js";
2
+ export * from "./auth-user-specs.util.js";
2
3
  export * from "./change-password.util.js";
3
4
  export * from "./create-new-refresh-token.util.js";
4
5
  export * from "./create-new-tokens.util.js";
@@ -1,3 +1,4 @@
1
1
  import type { ITokenResponse, IUserContext } from "@loomcore/common/models";
2
2
  import type { IDatabase } from "../../databases/models/index.js";
3
- export declare function logUserIn(database: IDatabase, userContext: IUserContext, deviceId: string): Promise<ITokenResponse>;
3
+ import type { UserService } from "../../services/user.service.js";
4
+ export declare function logUserIn(database: IDatabase, userContext: IUserContext, deviceId: string, userService?: UserService): Promise<ITokenResponse>;
@@ -3,7 +3,7 @@ import { createNewRefreshToken } from "./create-new-refresh-token.util.js";
3
3
  import { getAuthConfig } from "./get-auth-config.util.js";
4
4
  import { getExpiresOnFromSeconds } from "./get-expires-on-from-seconds.util.js";
5
5
  import { updateLastLoggedIn } from "./update-last-logged-in.util.js";
6
- export async function logUserIn(database, userContext, deviceId) {
6
+ export async function logUserIn(database, userContext, deviceId, userService) {
7
7
  const authConfig = getAuthConfig();
8
8
  const accessToken = generateJwt(userContext);
9
9
  const refreshTokenObject = await createNewRefreshToken(database, userContext.user._id, deviceId, userContext.organization?._id);
@@ -13,6 +13,6 @@ export async function logUserIn(database, userContext, deviceId) {
13
13
  refreshToken: refreshTokenObject.token,
14
14
  expiresOn: accessTokenExpiresOn,
15
15
  };
16
- updateLastLoggedIn(database, userContext.user._id).catch((err) => console.log(`Error updating lastLoggedIn: ${err}`));
16
+ updateLastLoggedIn(database, userContext.user._id, userService).catch((err) => console.log(`Error updating lastLoggedIn: ${err}`));
17
17
  return tokenResponse;
18
18
  }
@@ -1,4 +1,5 @@
1
- import { type IOrganization } from "@loomcore/common/models";
1
+ import { type IModelSpec, type IOrganization } from "@loomcore/common/models";
2
2
  import type { IDatabase } from "../../databases/models/index.js";
3
3
  import type { UpdateResult } from "../../databases/models/update-result.js";
4
- export declare function resetPassword(database: IDatabase, email: string, passwordResetToken: string, password: string, organization: IOrganization | null): Promise<UpdateResult>;
4
+ import { UserService } from "../../services/user.service.js";
5
+ export declare function resetPassword(database: IDatabase, email: string, passwordResetToken: string, password: string, organization: IOrganization | null, userService?: UserService, userSpec?: IModelSpec): Promise<UpdateResult>;
@@ -4,12 +4,11 @@ import { BadRequestError, ServerError } from "../../errors/index.js";
4
4
  import { PasswordResetTokenService } from "../../services/password-reset-token.service.js";
5
5
  import { UserService } from "../../services/user.service.js";
6
6
  import { changePassword } from "./change-password.util.js";
7
- export async function resetPassword(database, email, passwordResetToken, password, organization) {
8
- const validationErrors = entityUtils.validate(UserSpec, { password: password }, true, passwordValidator);
7
+ export async function resetPassword(database, email, passwordResetToken, password, organization, userService = new UserService(database), userSpec = UserSpec) {
8
+ const validationErrors = entityUtils.validate(userSpec, { password: password }, true, passwordValidator);
9
9
  entityUtils.handleValidationResult(validationErrors, "AuthService.resetPassword");
10
10
  const lowerCaseEmail = email.toLowerCase();
11
11
  const passwordResetTokenService = new PasswordResetTokenService(database);
12
- const userService = new UserService(database);
13
12
  const userContext = {
14
13
  ...EmptyUserContext,
15
14
  organization: organization ?? undefined,
@@ -31,7 +30,7 @@ export async function resetPassword(database, email, passwordResetToken, passwor
31
30
  throw new ServerError(`Unable to retrieve user for email: ${lowerCaseEmail}`);
32
31
  }
33
32
  userContext.user = user;
34
- const result = await changePassword(database, userContext, password);
33
+ const result = await changePassword(database, userContext, password, userService);
35
34
  console.log(`password changed using forgot-password for email: ${lowerCaseEmail}`);
36
35
  await passwordResetTokenService.deleteById(userContext, retrievedPasswordResetToken._id);
37
36
  console.log(`passwordResetToken deleted for email: ${lowerCaseEmail}`);
@@ -1,3 +1,4 @@
1
1
  import type { AppIdType } from "@loomcore/common/types";
2
2
  import type { IDatabase } from "../../databases/models/index.js";
3
- export declare function updateLastLoggedIn(database: IDatabase, userId: AppIdType): Promise<void>;
3
+ import { UserService } from "../../services/user.service.js";
4
+ export declare function updateLastLoggedIn(database: IDatabase, userId: AppIdType, userService?: UserService): Promise<void>;
@@ -1,12 +1,11 @@
1
1
  import { getSystemUserContext } from "@loomcore/common/models";
2
2
  import moment from "moment";
3
3
  import { UserService } from "../../services/user.service.js";
4
- export async function updateLastLoggedIn(database, userId) {
4
+ export async function updateLastLoggedIn(database, userId, userService = new UserService(database)) {
5
5
  try {
6
6
  const updates = {
7
7
  _lastLoggedIn: moment().utc().toDate(),
8
8
  };
9
- const userService = new UserService(database);
10
9
  await userService.update(getSystemUserContext(), { filters: { _id: { eq: userId } } }, updates);
11
10
  }
12
11
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomcore/api",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "private": false,
5
5
  "description": "Loom Core Api - An opinionated Node.js api using Typescript, Express, and MongoDb or PostgreSQL",
6
6
  "scripts": {