fix: Major bug cleanup pass 1
All checks were successful
Queue Release Build / prepare (push) Successful in 19s
Deploy Web Apps / deploy (push) Successful in 8m12s
Queue Release Build / build-windows (push) Successful in 27m44s
Queue Release Build / build-linux (push) Successful in 48m1s
Queue Release Build / build-android (push) Successful in 22m7s
Queue Release Build / finalize (push) Successful in 2m42s
All checks were successful
Queue Release Build / prepare (push) Successful in 19s
Deploy Web Apps / deploy (push) Successful in 8m12s
Queue Release Build / build-windows (push) Successful in 27m44s
Queue Release Build / build-linux (push) Successful in 48m1s
Queue Release Build / build-android (push) Successful in 22m7s
Queue Release Build / finalize (push) Successful in 2m42s
This commit is contained in:
@@ -41,7 +41,7 @@ function buildCorsOptions() {
|
||||
export function createApp(): express.Express {
|
||||
const app = express();
|
||||
|
||||
// Trust loopback proxies only — avoids express-rate-limit ERR_ERL_PERMISSIVE_TRUST_PROXY.
|
||||
// Trust loopback proxies only - avoids express-rate-limit ERR_ERL_PERMISSIVE_TRUST_PROXY.
|
||||
app.set('trust proxy', 'loopback');
|
||||
app.use(cors(buildCorsOptions()));
|
||||
app.use(express.json());
|
||||
|
||||
30
server/src/routes/user-registration.rules.spec.ts
Normal file
30
server/src/routes/user-registration.rules.spec.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
describe,
|
||||
it,
|
||||
expect
|
||||
} from 'vitest';
|
||||
import { isDuplicateUsernameError } from './user-registration.rules';
|
||||
|
||||
describe('user-registration.rules', () => {
|
||||
it('detects sqlite unique constraint failures on username', () => {
|
||||
expect(isDuplicateUsernameError({
|
||||
message: 'UNIQUE constraint failed: users.username'
|
||||
})).toBe(true);
|
||||
});
|
||||
|
||||
it('detects typeorm query failed errors with username constraint text', () => {
|
||||
expect(isDuplicateUsernameError({
|
||||
name: 'QueryFailedError',
|
||||
message: 'SQLITE_CONSTRAINT: UNIQUE constraint failed: users.username'
|
||||
})).toBe(true);
|
||||
});
|
||||
|
||||
it('ignores unrelated database errors', () => {
|
||||
expect(isDuplicateUsernameError({
|
||||
message: 'UNIQUE constraint failed: servers.id'
|
||||
})).toBe(false);
|
||||
|
||||
expect(isDuplicateUsernameError(new Error('connection lost'))).toBe(false);
|
||||
expect(isDuplicateUsernameError(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
11
server/src/routes/user-registration.rules.ts
Normal file
11
server/src/routes/user-registration.rules.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export function isDuplicateUsernameError(error: unknown): boolean {
|
||||
if (!error || typeof error !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const message = 'message' in error && typeof error.message === 'string'
|
||||
? error.message
|
||||
: '';
|
||||
|
||||
return message.includes('UNIQUE constraint failed: users.username');
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { hashPasswordForStorage, verifyPassword } from '../services/password-auth.service';
|
||||
import { issueSessionToken, revokeSessionToken } from '../services/session-auth.service';
|
||||
import { getAuthenticatedUserId, requireAuth } from '../middleware/require-auth';
|
||||
import { isDuplicateUsernameError } from './user-registration.rules';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -46,7 +47,16 @@ router.post('/register', async (req, res) => {
|
||||
createdAt: Date.now()
|
||||
};
|
||||
|
||||
await registerUser(user);
|
||||
try {
|
||||
await registerUser(user);
|
||||
} catch (error) {
|
||||
if (isDuplicateUsernameError(error)) {
|
||||
return res.status(409).json({ error: 'Username taken' });
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
const session = await issueSessionToken(user.id);
|
||||
|
||||
res.status(201).json(buildAuthResponse(user, session.token, session.expiresAt));
|
||||
|
||||
@@ -7,7 +7,11 @@ import {
|
||||
import { WebSocket } from 'ws';
|
||||
import { connectedUsers } from './state';
|
||||
import { ConnectedUser } from './types';
|
||||
import { broadcastToServer, findUserByOderId, findVoiceActiveConnection } from './broadcast';
|
||||
import {
|
||||
broadcastToServer,
|
||||
findUserByOderId,
|
||||
findVoiceActiveConnection
|
||||
} from './broadcast';
|
||||
|
||||
function createMockWs(): WebSocket & { sentMessages: string[] } {
|
||||
const sent: string[] = [];
|
||||
|
||||
@@ -134,12 +134,14 @@ describe('server websocket handler - multi-client sessions', () => {
|
||||
oderId: 'user-2',
|
||||
serverIds: new Set(['server-1'])
|
||||
});
|
||||
|
||||
createConnectedUser('conn-passive', {
|
||||
authenticated: true,
|
||||
oderId: 'user-1',
|
||||
serverIds: new Set(['server-1']),
|
||||
clientInstanceId: 'device-passive'
|
||||
});
|
||||
|
||||
const active = createConnectedUser('conn-active', {
|
||||
authenticated: true,
|
||||
oderId: 'user-1',
|
||||
@@ -169,6 +171,7 @@ describe('server websocket handler - multi-client sessions', () => {
|
||||
serverIds: new Set(['server-1']),
|
||||
clientInstanceId: 'device-b'
|
||||
});
|
||||
|
||||
const active = createConnectedUser('conn-active', {
|
||||
authenticated: true,
|
||||
oderId: 'user-1',
|
||||
@@ -197,6 +200,7 @@ describe('server websocket handler - multi-client sessions', () => {
|
||||
connectionScope: 'ws://localhost:3001',
|
||||
clientInstanceId: 'device-a'
|
||||
});
|
||||
|
||||
createConnectedUser('conn-new', {
|
||||
authenticated: false,
|
||||
connectionScope: 'ws://localhost:3001',
|
||||
|
||||
Reference in New Issue
Block a user