@meith/accounts 0.22.0 → 0.23.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meith/accounts",
3
- "version": "0.22.0",
3
+ "version": "0.23.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,6 +20,6 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "hash-wasm": "^4.12.0",
23
- "@meith/i18n": "0.22.0"
23
+ "@meith/i18n": "0.23.1"
24
24
  }
25
25
  }
@@ -0,0 +1,5 @@
1
+ import type { BanFilterRepository } from './ports'
2
+
3
+ export const BAN_FILTERS_NOT_CONSULTED: BanFilterRepository = {
4
+ listAll: async () => [],
5
+ }
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ export {
8
8
  type BanFilterType,
9
9
  matchBanFilter,
10
10
  } from './ban-filter'
11
+ export { BAN_FILTERS_NOT_CONSULTED } from './ban-filters-not-consulted'
11
12
  export { type BanInput, BanService, type BanServiceDeps } from './ban-service'
12
13
  export { foldIdentifier } from './case-fold'
13
14
  export {
@@ -114,10 +115,13 @@ export type {
114
115
  AuthEventKind,
115
116
  AuthEventRecord,
116
117
  AuthEventRepository,
118
+ BanFilterAdminRepository,
119
+ BanFilterRecord,
117
120
  BanFilterRepository,
118
121
  BanRecord,
119
122
  BanRepository,
120
123
  Clock,
124
+ CreateBanFilterInput,
121
125
  CreateBanInput,
122
126
  CredentialPurpose,
123
127
  CredentialTokenRepository,
@@ -1,9 +1,14 @@
1
- import type { BanFilter } from './ban-filter'
1
+ import { ValidationError } from '@meith/core'
2
+ import { msg } from '@meith/i18n'
3
+
4
+ import { assertUsableFilter, type BanFilter } from './ban-filter'
2
5
  import type {
3
6
  AccountRepository,
4
- BanFilterRepository,
7
+ BanFilterAdminRepository,
8
+ BanFilterRecord,
5
9
  BanRecord,
6
10
  BanRepository,
11
+ CreateBanFilterInput,
7
12
  CreateBanInput,
8
13
  } from './ports'
9
14
 
@@ -72,17 +77,60 @@ export class MemoryBans implements BanRepository {
72
77
  }
73
78
  }
74
79
 
75
- export class MemoryBanFilters implements BanFilterRepository {
76
- private readonly rows: BanFilter[] = []
80
+ export class MemoryBanFilters implements BanFilterAdminRepository {
81
+ private readonly rows: BanFilterRecord[] = []
77
82
  private nextId = 1
78
83
 
79
- add(type: BanFilter['type'], pattern: string): BanFilter {
80
- const row = { id: this.nextId++, type, pattern }
81
- this.rows.push(row)
82
- return row
84
+ add(type: BanFilter['type'], pattern: string, note: string | null = null): BanFilter {
85
+ return this.insert({ type, pattern, note, createdByUserId: null })
83
86
  }
84
87
 
85
88
  async listAll(): Promise<readonly BanFilter[]> {
86
- return [...this.rows]
89
+ return this.rows.map((row) => ({ id: row.id, type: row.type, pattern: row.pattern }))
90
+ }
91
+
92
+ async listForAdmin(): Promise<readonly BanFilterRecord[]> {
93
+ return [...this.rows].sort((a, b) => b.id - a.id)
94
+ }
95
+
96
+ async create(input: CreateBanFilterInput): Promise<number> {
97
+ assertUsableFilter(input.type, input.pattern)
98
+
99
+ const pattern = input.pattern.trim()
100
+ const note = input.note?.trim()
101
+
102
+ if (this.rows.some((row) => row.type === input.type && row.pattern === pattern)) {
103
+ throw new ValidationError(msg('error.accounts.ban-filter-already-held'))
104
+ }
105
+
106
+ return this.insert({
107
+ type: input.type,
108
+ pattern,
109
+ note: note === undefined || note === '' ? null : note,
110
+ createdByUserId: input.createdByUserId,
111
+ }).id
112
+ }
113
+
114
+ async remove(id: number): Promise<void> {
115
+ const at = this.rows.findIndex((row) => row.id === id)
116
+ if (at !== -1) this.rows.splice(at, 1)
117
+ }
118
+
119
+ private insert(input: {
120
+ type: BanFilter['type']
121
+ pattern: string
122
+ note: string | null
123
+ createdByUserId: number | null
124
+ }): BanFilterRecord {
125
+ const row: BanFilterRecord = {
126
+ id: this.nextId++,
127
+ type: input.type,
128
+ pattern: input.pattern,
129
+ note: input.note,
130
+ createdByUserId: input.createdByUserId,
131
+ createdAt: new Date(),
132
+ }
133
+ this.rows.push(row)
134
+ return row
87
135
  }
88
136
  }
package/src/ports.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { BanFilter } from './ban-filter'
1
+ import type { BanFilter, BanFilterType } from './ban-filter'
2
2
 
3
3
  export type AccountState = 'active' | 'awaiting_activation' | 'banned'
4
4
 
@@ -388,3 +388,24 @@ export interface BanRepository {
388
388
  export interface BanFilterRepository {
389
389
  listAll(): Promise<readonly BanFilter[]>
390
390
  }
391
+
392
+ export interface BanFilterRecord extends BanFilter {
393
+ readonly note: string | null
394
+ readonly createdByUserId: number | null
395
+ readonly createdAt: Date
396
+ }
397
+
398
+ export interface CreateBanFilterInput {
399
+ readonly type: BanFilterType
400
+ readonly pattern: string
401
+ readonly note?: string | null
402
+ readonly createdByUserId: number | null
403
+ }
404
+
405
+ export interface BanFilterAdminRepository extends BanFilterRepository {
406
+ listForAdmin(): Promise<readonly BanFilterRecord[]>
407
+
408
+ create(input: CreateBanFilterInput): Promise<number>
409
+
410
+ remove(id: number): Promise<void>
411
+ }
package/src/service.ts CHANGED
@@ -20,7 +20,7 @@ export interface IdentityDeps {
20
20
  readonly store: AccountStore
21
21
  readonly config: AuthConfig
22
22
  readonly clock?: Clock
23
- readonly banFilters?: BanFilterRepository
23
+ readonly banFilters: BanFilterRepository
24
24
  readonly bans?: BanLookup
25
25
  readonly secondFactor?: SecondFactorLookup
26
26
  }
@@ -117,7 +117,7 @@ export class IdentityService {
117
117
  private readonly config: AuthConfig
118
118
  private readonly now: Clock
119
119
 
120
- private readonly banFilters: BanFilterRepository | undefined
120
+ private readonly banFilters: BanFilterRepository
121
121
 
122
122
  private readonly bans: BanLookup | undefined
123
123
 
@@ -506,8 +506,6 @@ export class IdentityService {
506
506
  }
507
507
 
508
508
  private async assertNotFiltered(subject: BanFilterSubject): Promise<void> {
509
- if (!this.banFilters) return
510
-
511
509
  const match = matchBanFilter(await this.banFilters.listAll(), subject)
512
510
  if (match) {
513
511
  throw new ForbiddenError(msg('error.accounts.account-used-board-contact-administrator'))