mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Fix: password signup crashes with JSON parse error when MAIL_URL is configured (#10519)
When signing up with a password on deployments with MAIL_URL set, the account service returns token: undefined to enforce email confirmation. SignupForm.svelte was calling logIn() unconditionally, which triggered PUT /cookie without an Authorization header, returning a 401 with an unparseable body and leaving the user stuck on the signup page. - Guard logIn() in SignupForm.svelte with `result.token != null`, matching the pattern already used in doLoginNavigate() in utils.ts - Fix PUT /cookie 401 response to use Koa's ctx.status/ctx.body instead of raw ctx.res.writeHead/end, so the error body is correctly serialized - Add unit tests for the token guard logic Fixes #10518 Signed-off-by: Yulian Diaz <5605867+spatialy@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* Tests for the token guard fix in SignupForm.svelte (issue #10518).
|
||||
*
|
||||
* When MAIL_URL is configured the account service returns token: undefined
|
||||
* to force email confirmation. The signup handler must skip logIn() in that
|
||||
* case — calling logIn() without a token triggers PUT /cookie with no
|
||||
* Authorization header, which returns 401 and crashes the client's JSON
|
||||
* parser with "Unexpected token".
|
||||
*/
|
||||
|
||||
interface LoginInfo {
|
||||
account: string
|
||||
name?: string
|
||||
token?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirrors the fixed logic from SignupForm.svelte:
|
||||
*
|
||||
* if (result != null) {
|
||||
* if (result.token != null) {
|
||||
* await logIn(result)
|
||||
* }
|
||||
* goTo('confirmationSend')
|
||||
* }
|
||||
*/
|
||||
async function handleSignupResult (
|
||||
result: LoginInfo | null,
|
||||
logIn: (info: LoginInfo) => Promise<void>,
|
||||
goTo: (page: string) => void
|
||||
): Promise<void> {
|
||||
if (result != null) {
|
||||
if (result.token != null) {
|
||||
await logIn(result)
|
||||
}
|
||||
goTo('confirmationSend')
|
||||
}
|
||||
}
|
||||
|
||||
describe('SignupForm token guard (issue #10518)', () => {
|
||||
let logIn: jest.Mock
|
||||
let goTo: jest.Mock
|
||||
|
||||
beforeEach(() => {
|
||||
logIn = jest.fn().mockResolvedValue(undefined)
|
||||
goTo = jest.fn()
|
||||
})
|
||||
|
||||
it('skips logIn and redirects to confirmationSend when token is undefined (MAIL_URL configured)', async () => {
|
||||
// Server returns token: undefined when email confirmation is required
|
||||
const result: LoginInfo = { account: 'acc-uuid', name: 'Alice Smith', token: undefined }
|
||||
|
||||
await handleSignupResult(result, logIn, goTo)
|
||||
|
||||
expect(logIn).not.toHaveBeenCalled()
|
||||
expect(goTo).toHaveBeenCalledWith('confirmationSend')
|
||||
})
|
||||
|
||||
it('skips logIn and redirects to confirmationSend when token is absent', async () => {
|
||||
const result: LoginInfo = { account: 'acc-uuid' }
|
||||
|
||||
await handleSignupResult(result, logIn, goTo)
|
||||
|
||||
expect(logIn).not.toHaveBeenCalled()
|
||||
expect(goTo).toHaveBeenCalledWith('confirmationSend')
|
||||
})
|
||||
|
||||
it('calls logIn then redirects to confirmationSend when token is present (no MAIL_URL)', async () => {
|
||||
const result: LoginInfo = { account: 'acc-uuid', name: 'Bob Jones', token: 'eyJhbGciOiJIUzI1NiJ9.test' }
|
||||
|
||||
await handleSignupResult(result, logIn, goTo)
|
||||
|
||||
expect(logIn).toHaveBeenCalledTimes(1)
|
||||
expect(logIn).toHaveBeenCalledWith(result)
|
||||
expect(goTo).toHaveBeenCalledWith('confirmationSend')
|
||||
})
|
||||
|
||||
it('calls neither logIn nor goTo when result is null (signup error)', async () => {
|
||||
await handleSignupResult(null, logIn, goTo)
|
||||
|
||||
expect(logIn).not.toHaveBeenCalled()
|
||||
expect(goTo).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -95,7 +95,9 @@
|
||||
status = loginStatus
|
||||
|
||||
if (result != null) {
|
||||
await logIn(result)
|
||||
if (result.token != null) {
|
||||
await logIn(result)
|
||||
}
|
||||
goTo('confirmationSend')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,11 +306,8 @@ export function serveAccount (measureCtx: MeasureContext, brandings: BrandingMap
|
||||
router.put('/cookie', async (ctx) => {
|
||||
const token = extractToken(ctx.request.headers)
|
||||
if (token === undefined) {
|
||||
ctx.body = JSON.stringify({
|
||||
error: new Status(Severity.ERROR, platform.status.Unauthorized, {})
|
||||
})
|
||||
ctx.res.writeHead(401)
|
||||
ctx.res.end()
|
||||
ctx.status = 401
|
||||
ctx.body = { error: new Status(Severity.ERROR, platform.status.Unauthorized, {}) }
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user