@meith/web 0.35.0 → 0.35.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.
|
@@ -213,7 +213,7 @@ export async function POST(request: NextRequest): Promise<Response> {
|
|
|
213
213
|
return problem(await tr('authRoute.passkey.signInExpired'), 403)
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
await identity.
|
|
216
|
+
await identity.spendSecondFactorAttempt(pending.userId)
|
|
217
217
|
|
|
218
218
|
const proved = await service.proveOwnership({
|
|
219
219
|
userId: pending.userId,
|
|
@@ -224,7 +224,6 @@ export async function POST(request: NextRequest): Promise<Response> {
|
|
|
224
224
|
})
|
|
225
225
|
|
|
226
226
|
if (!proved) {
|
|
227
|
-
await identity.recordSecondFactorFailure(pending.userId)
|
|
228
227
|
await recordAuthEvent({ userId: pending.userId, kind: 'second_factor_failed' })
|
|
229
228
|
return problem(await tr('authRoute.passkey.notForAccount'), 403)
|
|
230
229
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meith/web",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.1",
|
|
4
4
|
"description": "The board itself: the Next.js app, and the forum-web bin that materializes it into an external board workspace.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -43,55 +43,55 @@
|
|
|
43
43
|
"react-dom": "19.2.8",
|
|
44
44
|
"tailwindcss": "^4.3.3",
|
|
45
45
|
"typescript": "7.0.2",
|
|
46
|
-
"@meith/accounts": "0.35.
|
|
47
|
-
"@meith/admin": "0.35.
|
|
48
|
-
"@meith/antispam": "0.35.
|
|
49
|
-
"@meith/api": "0.35.
|
|
50
|
-
"@meith/attachments": "0.35.
|
|
51
|
-
"@meith/authorization": "0.35.
|
|
52
|
-
"@meith/avatars": "0.35.
|
|
53
|
-
"@meith/backup": "0.35.
|
|
54
|
-
"@meith/board-digest": "0.35.
|
|
55
|
-
"@meith/core": "0.35.
|
|
56
|
-
"@meith/db": "0.35.
|
|
57
|
-
"@meith/demo": "0.35.
|
|
58
|
-
"@meith/drafts": "0.35.
|
|
59
|
-
"@meith/drivers": "0.35.
|
|
60
|
-
"@meith/events": "0.35.
|
|
61
|
-
"@meith/forums": "0.35.
|
|
62
|
-
"@meith/groups": "0.35.
|
|
63
|
-
"@meith/i18n": "0.35.
|
|
64
|
-
"@meith/import": "0.35.
|
|
65
|
-
"@meith/install": "0.35.
|
|
66
|
-
"@meith/mail": "0.35.
|
|
67
|
-
"@meith/markdown": "0.35.
|
|
68
|
-
"@meith/marketplace": "0.35.
|
|
69
|
-
"@meith/messages": "0.35.
|
|
70
|
-
"@meith/moderation": "0.35.
|
|
71
|
-
"@meith/notifications": "0.35.
|
|
72
|
-
"@meith/plugin-calendar": "0.35.
|
|
73
|
-
"@meith/plugin-dues": "0.35.
|
|
74
|
-
"@meith/plugin-kit": "0.35.
|
|
75
|
-
"@meith/polls": "0.35.
|
|
76
|
-
"@meith/posts": "0.35.
|
|
77
|
-
"@meith/profile-fields": "0.35.
|
|
78
|
-
"@meith/relations": "0.35.
|
|
79
|
-
"@meith/reputation": "0.35.
|
|
80
|
-
"@meith/runtime": "0.35.
|
|
81
|
-
"@meith/search": "0.35.
|
|
82
|
-
"@meith/settings": "0.35.
|
|
83
|
-
"@meith/signatures": "0.35.
|
|
84
|
-
"@meith/subscriptions": "0.35.
|
|
85
|
-
"@meith/tasks": "0.35.
|
|
86
|
-
"@meith/theme-clubhouse": "0.35.
|
|
87
|
-
"@meith/theme-default": "0.35.
|
|
88
|
-
"@meith/theme-kit": "0.35.
|
|
89
|
-
"@meith/theme-midnight": "0.35.
|
|
90
|
-
"@meith/theme-phasebook": "0.35.
|
|
91
|
-
"@meith/theme-raidframe": "0.35.
|
|
92
|
-
"@meith/threads": "0.35.
|
|
93
|
-
"@meith/ui": "0.35.
|
|
94
|
-
"@meith/upgrade": "0.35.
|
|
46
|
+
"@meith/accounts": "0.35.1",
|
|
47
|
+
"@meith/admin": "0.35.1",
|
|
48
|
+
"@meith/antispam": "0.35.1",
|
|
49
|
+
"@meith/api": "0.35.1",
|
|
50
|
+
"@meith/attachments": "0.35.1",
|
|
51
|
+
"@meith/authorization": "0.35.1",
|
|
52
|
+
"@meith/avatars": "0.35.1",
|
|
53
|
+
"@meith/backup": "0.35.1",
|
|
54
|
+
"@meith/board-digest": "0.35.1",
|
|
55
|
+
"@meith/core": "0.35.1",
|
|
56
|
+
"@meith/db": "0.35.1",
|
|
57
|
+
"@meith/demo": "0.35.1",
|
|
58
|
+
"@meith/drafts": "0.35.1",
|
|
59
|
+
"@meith/drivers": "0.35.1",
|
|
60
|
+
"@meith/events": "0.35.1",
|
|
61
|
+
"@meith/forums": "0.35.1",
|
|
62
|
+
"@meith/groups": "0.35.1",
|
|
63
|
+
"@meith/i18n": "0.35.1",
|
|
64
|
+
"@meith/import": "0.35.1",
|
|
65
|
+
"@meith/install": "0.35.1",
|
|
66
|
+
"@meith/mail": "0.35.1",
|
|
67
|
+
"@meith/markdown": "0.35.1",
|
|
68
|
+
"@meith/marketplace": "0.35.1",
|
|
69
|
+
"@meith/messages": "0.35.1",
|
|
70
|
+
"@meith/moderation": "0.35.1",
|
|
71
|
+
"@meith/notifications": "0.35.1",
|
|
72
|
+
"@meith/plugin-calendar": "0.35.1",
|
|
73
|
+
"@meith/plugin-dues": "0.35.1",
|
|
74
|
+
"@meith/plugin-kit": "0.35.1",
|
|
75
|
+
"@meith/polls": "0.35.1",
|
|
76
|
+
"@meith/posts": "0.35.1",
|
|
77
|
+
"@meith/profile-fields": "0.35.1",
|
|
78
|
+
"@meith/relations": "0.35.1",
|
|
79
|
+
"@meith/reputation": "0.35.1",
|
|
80
|
+
"@meith/runtime": "0.35.1",
|
|
81
|
+
"@meith/search": "0.35.1",
|
|
82
|
+
"@meith/settings": "0.35.1",
|
|
83
|
+
"@meith/signatures": "0.35.1",
|
|
84
|
+
"@meith/subscriptions": "0.35.1",
|
|
85
|
+
"@meith/tasks": "0.35.1",
|
|
86
|
+
"@meith/theme-clubhouse": "0.35.1",
|
|
87
|
+
"@meith/theme-default": "0.35.1",
|
|
88
|
+
"@meith/theme-kit": "0.35.1",
|
|
89
|
+
"@meith/theme-midnight": "0.35.1",
|
|
90
|
+
"@meith/theme-phasebook": "0.35.1",
|
|
91
|
+
"@meith/theme-raidframe": "0.35.1",
|
|
92
|
+
"@meith/threads": "0.35.1",
|
|
93
|
+
"@meith/ui": "0.35.1",
|
|
94
|
+
"@meith/upgrade": "0.35.1"
|
|
95
95
|
},
|
|
96
96
|
"scripts": {
|
|
97
97
|
"dev": "next dev",
|
|
@@ -55,28 +55,33 @@ export async function adminSignInAction(_prev: FormState, form: FormData): Promi
|
|
|
55
55
|
const config = await boardAuthConfig()
|
|
56
56
|
const attemptBucket = await adminReauthenticationBucket(actor.userId)
|
|
57
57
|
const attemptSince = new Date(Date.now() - config.lockoutMinutes * 60_000)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
|
|
59
|
+
let attemptId: number | null = null
|
|
60
|
+
if (config.maxLoginAttempts > 0) {
|
|
61
|
+
const spent = await accountStore.loginAttempts.recordFailureAndCount(
|
|
62
|
+
attemptBucket,
|
|
63
|
+
attemptSince,
|
|
64
|
+
new Date(),
|
|
65
|
+
)
|
|
66
|
+
attemptId = spent.id
|
|
67
|
+
if (spent.count > config.maxLoginAttempts) {
|
|
68
|
+
throw new ForbiddenError(msg('error.accounts.too-many-failed-attempts-please'))
|
|
69
|
+
}
|
|
64
70
|
}
|
|
65
71
|
|
|
66
|
-
const
|
|
67
|
-
await accountStore.loginAttempts.record(attemptBucket, false, new Date())
|
|
72
|
+
const auditFailure = async (): Promise<void> => {
|
|
68
73
|
await recordAdminAction({ action: 'admin.signin_failed' })
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
const password = text(form, 'password')
|
|
72
77
|
if (password === '') {
|
|
73
|
-
await
|
|
78
|
+
await auditFailure()
|
|
74
79
|
throw new ValidationError(msg('error.app.enter-password'))
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
const ok = await verifyPassword(password, account.passwordHash)
|
|
78
83
|
if (!ok) {
|
|
79
|
-
await
|
|
84
|
+
await auditFailure()
|
|
80
85
|
throw new ForbiddenError(msg('error.app.password-right'))
|
|
81
86
|
}
|
|
82
87
|
|
|
@@ -84,11 +89,12 @@ export async function adminSignInAction(_prev: FormState, form: FormData): Promi
|
|
|
84
89
|
const twoFactor = twoFactorService()
|
|
85
90
|
|
|
86
91
|
if (twoFactor !== null && (await twoFactor.isEnrolled(actor.userId))) {
|
|
92
|
+
if (attemptId !== null) await accountStore.loginAttempts.removeAttempt(attemptId)
|
|
87
93
|
await holdAdminSecondFactor(actor.userId, next)
|
|
88
94
|
target = '/admin'
|
|
89
95
|
} else {
|
|
90
96
|
if (twoFactor !== null && (await twoFactorRequiredForStaff())) {
|
|
91
|
-
await
|
|
97
|
+
await auditFailure()
|
|
92
98
|
throw new ForbiddenError(msg('adminAction.twoFactorRequired'))
|
|
93
99
|
}
|
|
94
100
|
|
|
@@ -133,28 +139,31 @@ export async function adminVerifySecondFactorAction(
|
|
|
133
139
|
const config = await boardAuthConfig()
|
|
134
140
|
const attemptBucket = await adminReauthenticationBucket(actor.userId)
|
|
135
141
|
const attemptSince = new Date(Date.now() - config.lockoutMinutes * 60_000)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
|
|
143
|
+
if (config.maxLoginAttempts > 0) {
|
|
144
|
+
const spent = await accountStore.loginAttempts.recordFailureAndCount(
|
|
145
|
+
attemptBucket,
|
|
146
|
+
attemptSince,
|
|
147
|
+
new Date(),
|
|
148
|
+
)
|
|
149
|
+
if (spent.count > config.maxLoginAttempts) {
|
|
150
|
+
throw new ForbiddenError(msg('error.accounts.too-many-failed-attempts-please'))
|
|
151
|
+
}
|
|
142
152
|
}
|
|
143
153
|
|
|
144
|
-
const
|
|
145
|
-
await accountStore.loginAttempts.record(attemptBucket, false, new Date())
|
|
154
|
+
const auditFailure = async (): Promise<void> => {
|
|
146
155
|
await recordAdminAction({ action: 'admin.signin_failed' })
|
|
147
156
|
}
|
|
148
157
|
|
|
149
158
|
const code = text(form, 'code')
|
|
150
159
|
if (code === '') {
|
|
151
|
-
await
|
|
160
|
+
await auditFailure()
|
|
152
161
|
throw new ValidationError(msg('error.app.enter-code-from-authenticator-app'))
|
|
153
162
|
}
|
|
154
163
|
|
|
155
164
|
const outcome = await twoFactor.verify({ userId: pending.userId, code })
|
|
156
165
|
if (outcome.status !== 'ok') {
|
|
157
|
-
await
|
|
166
|
+
await auditFailure()
|
|
158
167
|
throw new ForbiddenError(
|
|
159
168
|
msg(outcome.status === 'replayed' ? 'adminAction.codeReplayed' : 'adminAction.codeWrong'),
|
|
160
169
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use server'
|
|
2
2
|
|
|
3
3
|
import { redirect } from 'next/navigation'
|
|
4
|
+
import { after } from 'next/server'
|
|
4
5
|
|
|
5
6
|
import { type AuthConfig, foldIdentifier, hashToken, type LoginBucket } from '@meith/accounts'
|
|
6
7
|
import { env, logger } from '@meith/core'
|
|
@@ -344,12 +345,11 @@ export async function verifySecondFactorAction(
|
|
|
344
345
|
if (service === null) return { error: await tr('authAction.secondFactorExpired') }
|
|
345
346
|
|
|
346
347
|
try {
|
|
347
|
-
await identity.
|
|
348
|
+
await identity.spendSecondFactorAttempt(pending.userId)
|
|
348
349
|
|
|
349
350
|
const outcome = await service.verify({ userId: pending.userId, code })
|
|
350
351
|
|
|
351
352
|
if (outcome.status !== 'ok') {
|
|
352
|
-
await identity.recordSecondFactorFailure(pending.userId)
|
|
353
353
|
await recordAuthEvent({ userId: pending.userId, kind: 'second_factor_failed' })
|
|
354
354
|
return {
|
|
355
355
|
error: await tr(
|
|
@@ -440,9 +440,10 @@ export async function requestResetAction(_prev: FormState, form: FormData): Prom
|
|
|
440
440
|
const { token, userId } = await identity.requestPasswordReset(email)
|
|
441
441
|
|
|
442
442
|
if (token !== null && userId !== null) {
|
|
443
|
-
const
|
|
444
|
-
if (account !== null) {
|
|
443
|
+
const deliver = async (): Promise<void> => {
|
|
445
444
|
try {
|
|
445
|
+
const account = await getContainer().accountStore.accounts.findById(userId)
|
|
446
|
+
if (account === null) return
|
|
446
447
|
await sendPasswordResetEmail({
|
|
447
448
|
token,
|
|
448
449
|
email: account.email,
|
|
@@ -456,6 +457,12 @@ export async function requestResetAction(_prev: FormState, form: FormData): Prom
|
|
|
456
457
|
)
|
|
457
458
|
}
|
|
458
459
|
}
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
after(deliver)
|
|
463
|
+
} catch {
|
|
464
|
+
await deliver()
|
|
465
|
+
}
|
|
459
466
|
}
|
|
460
467
|
|
|
461
468
|
if (token && env.NODE_ENV === 'development') {
|
package/src/server/federation.ts
CHANGED
|
@@ -54,7 +54,9 @@ export async function federationProvider(id: string): Promise<IdentityProvider |
|
|
|
54
54
|
if (!isProviderKind(id)) return null
|
|
55
55
|
if (getContainer().dataSource !== 'postgres') return null
|
|
56
56
|
|
|
57
|
-
return providerFor(id, federationOptions(await getSettingsUncached())
|
|
57
|
+
return providerFor(id, federationOptions(await getSettingsUncached()), {
|
|
58
|
+
allowPrivateHosts: env.OIDC_ALLOW_PRIVATE_HOSTS,
|
|
59
|
+
})
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
export async function signInProviders(): Promise<readonly ProviderButton[]> {
|
|
@@ -17,7 +17,7 @@ import { planUpgrade, type UpgradeState, upgradeNotice } from '@meith/upgrade'
|
|
|
17
17
|
|
|
18
18
|
import { activeDefinitions } from './plugin-host'
|
|
19
19
|
|
|
20
|
-
export const CODE_VERSION = '0.35.
|
|
20
|
+
export const CODE_VERSION = '0.35.1'
|
|
21
21
|
|
|
22
22
|
export interface UpgradeApplied {
|
|
23
23
|
readonly plugins: readonly string[]
|