This commit is contained in:
seb
2026-10-29 16:27:02 +01:00
parent 005b48ff66
commit 676a40d4fe
6 changed files with 50 additions and 16 deletions

View File

@@ -235,7 +235,7 @@ _none_
- Returns: `200 { message: str }`
#### Current user — `GET /auth/me`
_none_
- Returns: `200 { user: T32, googleClientId: str }`
- Returns: `200 { user: T32, googleClientId: str, schemaVersion: int }`
#### Set selected company — `PUT /auth/selected-company`
- Params: `body: { companyId: int }`
- Returns: `200 { user: T32 }`

View File

@@ -6,7 +6,7 @@ Current user
Returns the authenticated user from the session cookie and the Google client ID.
`user.picture` is the stored profile image as a base64 string when present.
Unauthenticated calls still receive `googleClientId` with 401.
Unauthenticated calls still receive `googleClientId` and `schemaVersion` with 401.
## Response `200`
@@ -21,7 +21,8 @@ Authenticated
"selectedCompanyId": 1,
"picture": "…"
},
"googleClientId": "…"
"googleClientId": "…",
"schemaVersion": 1
}
```
@@ -29,7 +30,7 @@ Authenticated
| Status | Description |
|--------|-------------|
| 401 | Unauthorized — still includes `googleClientId` for the frontend |
| 401 | Unauthorized — still includes `googleClientId` and `schemaVersion` for the frontend |
---

View File

@@ -1,5 +1,16 @@
import env from '../../config/env.js';
import { getSchemaVersion } from '../../db/store.js';
export function respondWithAuthenticatedUser(res, status, body) {
return res.status(status).json({ ...body, googleClientId: env.googleClientId });
export async function respondWithAuthenticatedUser(res, status, body) {
let schemaVersion = null;
try {
schemaVersion = await getSchemaVersion();
} catch {
// ignore if database not accessible
}
return res.status(status).json({
...body,
googleClientId: env.googleClientId,
schemaVersion,
});
}

View File

@@ -47,7 +47,7 @@ export function clearCachedCompanyUsers(companyId) {
}
}
function respondWithAuthPayload(res, req, status, body) {
async function respondWithAuthPayload(res, req, status, body) {
if (req.path === '/api/auth/me') {
return respondWithAuthenticatedUser(res, status, body);
}
@@ -85,7 +85,7 @@ export const authMiddleware = async (req, res, next) => {
const isDocs = req.path.startsWith('/docs') || req.path.startsWith('/api/docs');
if (!token) {
if (isDocs || isOAuthAuthorize) return next();
return respondWithAuthPayload(res, req, 401, { error: 'Unauthorized' });
return await respondWithAuthPayload(res, req, 401, { error: 'Unauthorized' });
}
try {
@@ -94,7 +94,7 @@ export const authMiddleware = async (req, res, next) => {
if (!Number.isInteger(authId) || authId <= 0) {
if (req.cookies.token) clearTokenCookie(res);
if (isDocs) return next();
return respondWithAuthPayload(res, req, 401, { error: 'Invalid token' });
return await respondWithAuthPayload(res, req, 401, { error: 'Invalid token' });
}
let user = getCachedUser(authId);
@@ -111,7 +111,7 @@ export const authMiddleware = async (req, res, next) => {
clearCachedUser(authId);
if (req.cookies.token) clearTokenCookie(res);
if (isDocs) return next();
return respondWithAuthPayload(res, req, 403, { error: 'User is no longer authorized' });
return await respondWithAuthPayload(res, req, 403, { error: 'User is no longer authorized' });
}
req.user = {
@@ -133,7 +133,7 @@ export const authMiddleware = async (req, res, next) => {
} catch {
if (req.cookies.token) clearTokenCookie(res);
if (isDocs || isOAuthAuthorize) return next();
return respondWithAuthPayload(res, req, 401, { error: 'Invalid token' });
return await respondWithAuthPayload(res, req, 401, { error: 'Invalid token' });
}
};

View File

@@ -17,7 +17,7 @@ export function register(app, basePath = '') {
* description: |
* Returns the authenticated user from the session cookie and the Google client ID.
* `user.picture` is the stored profile image as a base64 string when present.
* Unauthenticated calls still receive `googleClientId` with 401.
* Unauthenticated calls still receive `googleClientId` and `schemaVersion` with 401.
* security:
* - cookieAuth: []
* x-markdown: auth/me.md
@@ -33,8 +33,11 @@ export function register(app, basePath = '') {
* $ref: '#/components/schemas/User'
* googleClientId:
* type: string
* schemaVersion:
* type: integer
* description: Current database schema version
* 401:
* description: Unauthorized — still includes `googleClientId` for the frontend
* description: Unauthorized — still includes `googleClientId` and `schemaVersion` for the frontend
* content:
* application/json:
* schema:
@@ -44,12 +47,15 @@ export function register(app, basePath = '') {
* type: string
* googleClientId:
* type: string
* schemaVersion:
* type: integer
* description: Current database schema version
*/
app.get(`${basePath}/auth/me`, async (req, res) => {
try {
const row = await db.getById(req.user.id);
if (!row) {
return respondWithAuthenticatedUser(res, 403, { error: 'User is no longer authorized' });
return await respondWithAuthenticatedUser(res, 403, { error: 'User is no longer authorized' });
}
const pictureBinary = toPictureBuffer(row.pictureBinary);
@@ -59,10 +65,10 @@ export function register(app, basePath = '') {
selectedCompanyId: row.selectedCompanyId,
...(pictureBinary && { picture: pictureBinary.toString('base64') }),
};
respondWithAuthenticatedUser(res, 200, { user });
await respondWithAuthenticatedUser(res, 200, { user });
} catch (error) {
console.error('Get current user failed:', error);
respondWithAuthenticatedUser(res, 500, { error: 'Failed to load user' });
await respondWithAuthenticatedUser(res, 500, { error: 'Failed to load user' });
}
});
}

View File

@@ -10,6 +10,7 @@ const schemaLog = createLogger('schema');
let provider;
let store;
let connecting;
let schemaVersion;
const STORE_NAMESPACES = [
'companyuser',
@@ -149,6 +150,7 @@ export async function getStore() {
provider = createProvider();
try {
await provider.initialize((msg) => schemaLog.info(msg));
schemaVersion = await provider.getSchemaVersion();
} catch (err) {
schemaLog.error('Database schema initialization failed:', err);
throw err;
@@ -168,6 +170,19 @@ export async function getProvider() {
return provider;
}
/**
* Returns the current database schema version.
* @returns {Promise<number>}
*/
export async function getSchemaVersion() {
if (schemaVersion != null) {
return schemaVersion;
}
const prov = await getProvider();
schemaVersion = await prov.getSchemaVersion();
return schemaVersion;
}
export async function ensureConnection() {
await getStore();
}
@@ -177,6 +192,7 @@ export async function closeStore() {
await provider.close();
provider = undefined;
store = undefined;
schemaVersion = undefined;
}
}