@flowerforce/flowerbase 1.4.2-beta.3 → 1.4.2-beta.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.
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/auth/controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAQzC;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,eAAe,iBA+IxD"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/auth/controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAQzC;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,eAAe,iBA0JxD"}
@@ -31,6 +31,14 @@ function authController(app) {
31
31
  catch (error) {
32
32
  console.error('Failed to ensure refresh token TTL index', error);
33
33
  }
34
+ try {
35
+ yield db.collection(authCollection).createIndex({ email: 1 }, {
36
+ unique: true
37
+ });
38
+ }
39
+ catch (error) {
40
+ console.error('Failed to ensure auth email unique index', error);
41
+ }
34
42
  app.addHook(HANDLER_TYPE, app.jwtAuthentication);
35
43
  /**
36
44
  * Endpoint to retrieve the authenticated user's profile.
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../../src/auth/providers/anon-user/controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAOzC;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,eAAe,iBA8E5D"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../../src/auth/providers/anon-user/controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAOzC;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,eAAe,iBAmE5D"}
@@ -10,9 +10,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.anonUserController = anonUserController;
13
+ const bson_1 = require("bson");
13
14
  const constants_1 = require("../../../constants");
14
- const crypto_1 = require("../../../utils/crypto");
15
15
  const handleUserRegistration_model_1 = require("../../../shared/models/handleUserRegistration.model");
16
+ const crypto_1 = require("../../../utils/crypto");
16
17
  const utils_1 = require("../../utils");
17
18
  /**
18
19
  * Controller for handling anonymous user login.
@@ -41,30 +42,23 @@ function anonUserController(app) {
41
42
  throw new Error('Anonymous authentication disabled');
42
43
  }
43
44
  const now = new Date();
44
- const insertResult = yield db.collection(authCollection).insertOne({
45
+ const userId = new bson_1.ObjectId();
46
+ const anonEmail = `anon-${userId.toString()}@users.invalid`;
47
+ yield db.collection(authCollection).insertOne({
48
+ _id: userId,
49
+ email: anonEmail,
45
50
  status: 'confirmed',
46
51
  createdAt: now,
47
52
  custom_data: {},
48
53
  identities: [
49
54
  {
55
+ id: userId.toString(),
56
+ provider_id: userId.toString(),
50
57
  provider_type: handleUserRegistration_model_1.PROVIDER.ANON_USER,
51
58
  provider_data: {}
52
59
  }
53
60
  ]
54
61
  });
55
- const userId = insertResult.insertedId;
56
- yield db.collection(authCollection).updateOne({ _id: userId }, {
57
- $set: {
58
- identities: [
59
- {
60
- id: userId.toString(),
61
- provider_id: userId.toString(),
62
- provider_type: handleUserRegistration_model_1.PROVIDER.ANON_USER,
63
- provider_data: {}
64
- }
65
- ]
66
- }
67
- });
68
62
  const currentUserData = {
69
63
  _id: userId,
70
64
  user_data: {}
@@ -15,10 +15,10 @@ exports.LOGIN_SCHEMA = {
15
15
  username: {
16
16
  type: 'string',
17
17
  pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
18
- minLength: 3,
18
+ minLength: 5,
19
19
  maxLength: 254
20
20
  },
21
- password: { type: 'string', minLength: 8, maxLength: 128 }
21
+ password: { type: 'string', minLength: 6, maxLength: 128 }
22
22
  },
23
23
  required: ['username', 'password']
24
24
  }
@@ -30,7 +30,7 @@ exports.RESET_SEND_SCHEMA = {
30
30
  email: {
31
31
  type: 'string',
32
32
  pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
33
- minLength: 3,
33
+ minLength: 5,
34
34
  maxLength: 254
35
35
  }
36
36
  },
@@ -44,10 +44,10 @@ exports.RESET_CALL_SCHEMA = {
44
44
  email: {
45
45
  type: 'string',
46
46
  pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
47
- minLength: 3,
47
+ minLength: 5,
48
48
  maxLength: 254
49
49
  },
50
- password: { type: 'string', minLength: 8, maxLength: 128 },
50
+ password: { type: 'string', minLength: 6, maxLength: 128 },
51
51
  arguments: { type: 'array' }
52
52
  },
53
53
  required: ['email', 'password']
@@ -57,7 +57,7 @@ exports.CONFIRM_RESET_SCHEMA = {
57
57
  body: {
58
58
  type: 'object',
59
59
  properties: {
60
- password: { type: 'string', minLength: 8, maxLength: 128 },
60
+ password: { type: 'string', minLength: 6, maxLength: 128 },
61
61
  token: { type: 'string' },
62
62
  tokenId: { type: 'string' }
63
63
  },
@@ -82,10 +82,10 @@ exports.REGISTRATION_SCHEMA = {
82
82
  email: {
83
83
  type: 'string',
84
84
  pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
85
- minLength: 3,
85
+ minLength: 5,
86
86
  maxLength: 254
87
87
  },
88
- password: { type: 'string', minLength: 8, maxLength: 128 }
88
+ password: { type: 'string', minLength: 6, maxLength: 128 }
89
89
  },
90
90
  required: ['email', 'password']
91
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowerforce/flowerbase",
3
- "version": "1.4.2-beta.3",
3
+ "version": "1.4.2-beta.4",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,51 @@
1
+ import { authController } from '../controller'
2
+
3
+ jest.mock('../../constants', () => ({
4
+ AUTH_CONFIG: {
5
+ authCollection: 'auth_users',
6
+ refreshTokensCollection: 'refresh_tokens',
7
+ userCollection: 'users',
8
+ user_id_field: 'id'
9
+ },
10
+ DB_NAME: 'test-db',
11
+ DEFAULT_CONFIG: {
12
+ REFRESH_TOKEN_TTL_DAYS: 1
13
+ }
14
+ }))
15
+
16
+ describe('authController', () => {
17
+ it('creates a unique email index on the auth collection', async () => {
18
+ const authCollection = {
19
+ createIndex: jest.fn().mockResolvedValue('ok')
20
+ }
21
+ const refreshCollection = {
22
+ createIndex: jest.fn().mockResolvedValue('ok')
23
+ }
24
+ const db = {
25
+ collection: jest.fn((name: string) => {
26
+ if (name === 'auth_users') {
27
+ return authCollection
28
+ }
29
+ if (name === 'refresh_tokens') {
30
+ return refreshCollection
31
+ }
32
+ return { createIndex: jest.fn().mockResolvedValue('ok') }
33
+ })
34
+ }
35
+ const app = {
36
+ mongo: { client: { db: jest.fn().mockReturnValue(db) } },
37
+ addHook: jest.fn(),
38
+ get: jest.fn(),
39
+ post: jest.fn(),
40
+ delete: jest.fn(),
41
+ jwtAuthentication: jest.fn()
42
+ }
43
+
44
+ await authController(app as unknown as never)
45
+
46
+ expect(authCollection.createIndex).toHaveBeenCalledWith(
47
+ { email: 1 },
48
+ { unique: true }
49
+ )
50
+ })
51
+ })
@@ -27,6 +27,17 @@ export async function authController(app: FastifyInstance) {
27
27
  console.error('Failed to ensure refresh token TTL index', error)
28
28
  }
29
29
 
30
+ try {
31
+ await db.collection(authCollection).createIndex(
32
+ { email: 1 },
33
+ {
34
+ unique: true
35
+ }
36
+ )
37
+ } catch (error) {
38
+ console.error('Failed to ensure auth email unique index', error)
39
+ }
40
+
30
41
  app.addHook(HANDLER_TYPE, app.jwtAuthentication)
31
42
 
32
43
  /**
@@ -0,0 +1,82 @@
1
+ import { anonUserController } from '../controller'
2
+ import { PROVIDER } from '../../../../shared/models/handleUserRegistration.model'
3
+
4
+ jest.mock('../../../../constants', () => ({
5
+ AUTH_CONFIG: {
6
+ authCollection: 'auth_users',
7
+ refreshTokensCollection: 'refresh_tokens',
8
+ providers: {
9
+ 'anon-user': { disabled: false }
10
+ }
11
+ },
12
+ DB_NAME: 'test-db',
13
+ DEFAULT_CONFIG: {
14
+ REFRESH_TOKEN_TTL_DAYS: 1,
15
+ ANON_USER_TTL_SECONDS: 3600
16
+ }
17
+ }))
18
+
19
+ describe('anonUserController', () => {
20
+ it('inserts anon users with a generated email', async () => {
21
+ let insertedDoc: Record<string, unknown> | undefined
22
+ const authCollection = {
23
+ createIndex: jest.fn().mockResolvedValue('ok'),
24
+ insertOne: jest.fn(async (doc: Record<string, unknown>) => {
25
+ insertedDoc = doc
26
+ return { insertedId: doc._id }
27
+ })
28
+ }
29
+ const refreshCollection = {
30
+ insertOne: jest.fn().mockResolvedValue({})
31
+ }
32
+ const db = {
33
+ collection: jest.fn((name: string) => {
34
+ if (name === 'auth_users') {
35
+ return authCollection
36
+ }
37
+ if (name === 'refresh_tokens') {
38
+ return refreshCollection
39
+ }
40
+ return authCollection
41
+ })
42
+ }
43
+
44
+ let loginHandler: ((...args: unknown[]) => unknown) | undefined
45
+ const app = {
46
+ mongo: { client: { db: jest.fn().mockReturnValue(db) } },
47
+ post: jest.fn((path: string, handler: (...args: unknown[]) => unknown) => {
48
+ loginHandler = handler
49
+ })
50
+ }
51
+
52
+ await anonUserController(app as unknown as never)
53
+
54
+ const context = {
55
+ createRefreshToken: jest.fn(() => 'refresh'),
56
+ createAccessToken: jest.fn(() => 'access')
57
+ }
58
+
59
+ await (loginHandler as (...args: unknown[]) => Promise<unknown>).call(context, {})
60
+
61
+ const doc = insertedDoc as {
62
+ _id: { toString: () => string }
63
+ email: string
64
+ identities: Array<{
65
+ id?: string
66
+ provider_id?: string
67
+ provider_type?: string
68
+ provider_data?: Record<string, unknown>
69
+ }>
70
+ }
71
+
72
+ expect(doc.email).toBe(`anon-${doc._id.toString()}@users.invalid`)
73
+ expect(doc.identities[0]).toEqual(
74
+ expect.objectContaining({
75
+ id: doc._id.toString(),
76
+ provider_id: doc._id.toString(),
77
+ provider_type: PROVIDER.ANON_USER,
78
+ provider_data: {}
79
+ })
80
+ )
81
+ })
82
+ })
@@ -1,7 +1,8 @@
1
+ import { ObjectId } from 'bson'
1
2
  import { FastifyInstance } from 'fastify'
2
3
  import { AUTH_CONFIG, DB_NAME, DEFAULT_CONFIG } from '../../../constants'
3
- import { hashToken } from '../../../utils/crypto'
4
4
  import { PROVIDER } from '../../../shared/models/handleUserRegistration.model'
5
+ import { hashToken } from '../../../utils/crypto'
5
6
  import { AUTH_ENDPOINTS } from '../../utils'
6
7
  import { LoginDto } from './dtos'
7
8
 
@@ -37,35 +38,24 @@ export async function anonUserController(app: FastifyInstance) {
37
38
  }
38
39
 
39
40
  const now = new Date()
40
- const insertResult = await db.collection(authCollection!).insertOne({
41
+ const userId = new ObjectId()
42
+ const anonEmail = `anon-${userId.toString()}@users.invalid`
43
+ await db.collection(authCollection!).insertOne({
44
+ _id: userId,
45
+ email: anonEmail,
41
46
  status: 'confirmed',
42
47
  createdAt: now,
43
48
  custom_data: {},
44
49
  identities: [
45
50
  {
51
+ id: userId.toString(),
52
+ provider_id: userId.toString(),
46
53
  provider_type: PROVIDER.ANON_USER,
47
54
  provider_data: {}
48
55
  }
49
56
  ]
50
57
  })
51
58
 
52
- const userId = insertResult.insertedId
53
- await db.collection(authCollection!).updateOne(
54
- { _id: userId },
55
- {
56
- $set: {
57
- identities: [
58
- {
59
- id: userId.toString(),
60
- provider_id: userId.toString(),
61
- provider_type: PROVIDER.ANON_USER,
62
- provider_data: {}
63
- }
64
- ]
65
- }
66
- }
67
- )
68
-
69
59
  const currentUserData = {
70
60
  _id: userId,
71
61
  user_data: {}
package/src/auth/utils.ts CHANGED
@@ -11,10 +11,10 @@ export const LOGIN_SCHEMA = {
11
11
  username: {
12
12
  type: 'string',
13
13
  pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
14
- minLength: 3,
14
+ minLength: 5,
15
15
  maxLength: 254
16
16
  },
17
- password: { type: 'string', minLength: 8, maxLength: 128 }
17
+ password: { type: 'string', minLength: 6, maxLength: 128 }
18
18
  },
19
19
  required: ['username', 'password']
20
20
  }
@@ -27,7 +27,7 @@ export const RESET_SEND_SCHEMA = {
27
27
  email: {
28
28
  type: 'string',
29
29
  pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
30
- minLength: 3,
30
+ minLength: 5,
31
31
  maxLength: 254
32
32
  }
33
33
  },
@@ -42,10 +42,10 @@ export const RESET_CALL_SCHEMA = {
42
42
  email: {
43
43
  type: 'string',
44
44
  pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
45
- minLength: 3,
45
+ minLength: 5,
46
46
  maxLength: 254
47
47
  },
48
- password: { type: 'string', minLength: 8, maxLength: 128 },
48
+ password: { type: 'string', minLength: 6, maxLength: 128 },
49
49
  arguments: { type: 'array' }
50
50
  },
51
51
  required: ['email', 'password']
@@ -56,7 +56,7 @@ export const CONFIRM_RESET_SCHEMA = {
56
56
  body: {
57
57
  type: 'object',
58
58
  properties: {
59
- password: { type: 'string', minLength: 8, maxLength: 128 },
59
+ password: { type: 'string', minLength: 6, maxLength: 128 },
60
60
  token: { type: 'string' },
61
61
  tokenId: { type: 'string' }
62
62
  },
@@ -84,10 +84,10 @@ export const REGISTRATION_SCHEMA = {
84
84
  email: {
85
85
  type: 'string',
86
86
  pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
87
- minLength: 3,
87
+ minLength: 5,
88
88
  maxLength: 254
89
89
  },
90
- password: { type: 'string', minLength: 8, maxLength: 128 }
90
+ password: { type: 'string', minLength: 6, maxLength: 128 }
91
91
  },
92
92
  required: ['email', 'password']
93
93
  }