u
This commit is contained in:
@@ -235,7 +235,7 @@ _none_
|
|||||||
- Returns: `200 { message: str }`
|
- Returns: `200 { message: str }`
|
||||||
#### Current user — `GET /auth/me`
|
#### Current user — `GET /auth/me`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { user: T32, googleClientId: str }`
|
- Returns: `200 { user: T32, googleClientId: str, schemaVersion: int }`
|
||||||
#### Set selected company — `PUT /auth/selected-company`
|
#### Set selected company — `PUT /auth/selected-company`
|
||||||
- Params: `body: { companyId: int }`
|
- Params: `body: { companyId: int }`
|
||||||
- Returns: `200 { user: T32 }`
|
- Returns: `200 { user: T32 }`
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Current user
|
|||||||
|
|
||||||
Returns the authenticated user from the session cookie and the Google client ID.
|
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.
|
`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`
|
## Response `200`
|
||||||
@@ -21,7 +21,8 @@ Authenticated
|
|||||||
"selectedCompanyId": 1,
|
"selectedCompanyId": 1,
|
||||||
"picture": "…"
|
"picture": "…"
|
||||||
},
|
},
|
||||||
"googleClientId": "…"
|
"googleClientId": "…",
|
||||||
|
"schemaVersion": 1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ Authenticated
|
|||||||
|
|
||||||
| Status | Description |
|
| Status | Description |
|
||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| 401 | Unauthorized — still includes `googleClientId` for the frontend |
|
| 401 | Unauthorized — still includes `googleClientId` and `schemaVersion` for the frontend |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
import env from '../../config/env.js';
|
import env from '../../config/env.js';
|
||||||
|
import { getSchemaVersion } from '../../db/store.js';
|
||||||
|
|
||||||
export function respondWithAuthenticatedUser(res, status, body) {
|
export async function respondWithAuthenticatedUser(res, status, body) {
|
||||||
return res.status(status).json({ ...body, googleClientId: env.googleClientId });
|
let schemaVersion = null;
|
||||||
|
try {
|
||||||
|
schemaVersion = await getSchemaVersion();
|
||||||
|
} catch {
|
||||||
|
// ignore if database not accessible
|
||||||
|
}
|
||||||
|
return res.status(status).json({
|
||||||
|
...body,
|
||||||
|
googleClientId: env.googleClientId,
|
||||||
|
schemaVersion,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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') {
|
if (req.path === '/api/auth/me') {
|
||||||
return respondWithAuthenticatedUser(res, status, body);
|
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');
|
const isDocs = req.path.startsWith('/docs') || req.path.startsWith('/api/docs');
|
||||||
if (!token) {
|
if (!token) {
|
||||||
if (isDocs || isOAuthAuthorize) return next();
|
if (isDocs || isOAuthAuthorize) return next();
|
||||||
return respondWithAuthPayload(res, req, 401, { error: 'Unauthorized' });
|
return await respondWithAuthPayload(res, req, 401, { error: 'Unauthorized' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -94,7 +94,7 @@ export const authMiddleware = async (req, res, next) => {
|
|||||||
if (!Number.isInteger(authId) || authId <= 0) {
|
if (!Number.isInteger(authId) || authId <= 0) {
|
||||||
if (req.cookies.token) clearTokenCookie(res);
|
if (req.cookies.token) clearTokenCookie(res);
|
||||||
if (isDocs) return next();
|
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);
|
let user = getCachedUser(authId);
|
||||||
@@ -111,7 +111,7 @@ export const authMiddleware = async (req, res, next) => {
|
|||||||
clearCachedUser(authId);
|
clearCachedUser(authId);
|
||||||
if (req.cookies.token) clearTokenCookie(res);
|
if (req.cookies.token) clearTokenCookie(res);
|
||||||
if (isDocs) return next();
|
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 = {
|
req.user = {
|
||||||
@@ -133,7 +133,7 @@ export const authMiddleware = async (req, res, next) => {
|
|||||||
} catch {
|
} catch {
|
||||||
if (req.cookies.token) clearTokenCookie(res);
|
if (req.cookies.token) clearTokenCookie(res);
|
||||||
if (isDocs || isOAuthAuthorize) return next();
|
if (isDocs || isOAuthAuthorize) return next();
|
||||||
return respondWithAuthPayload(res, req, 401, { error: 'Invalid token' });
|
return await respondWithAuthPayload(res, req, 401, { error: 'Invalid token' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function register(app, basePath = '') {
|
|||||||
* description: |
|
* description: |
|
||||||
* Returns the authenticated user from the session cookie and the Google client ID.
|
* 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.
|
* `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:
|
* security:
|
||||||
* - cookieAuth: []
|
* - cookieAuth: []
|
||||||
* x-markdown: auth/me.md
|
* x-markdown: auth/me.md
|
||||||
@@ -33,8 +33,11 @@ export function register(app, basePath = '') {
|
|||||||
* $ref: '#/components/schemas/User'
|
* $ref: '#/components/schemas/User'
|
||||||
* googleClientId:
|
* googleClientId:
|
||||||
* type: string
|
* type: string
|
||||||
|
* schemaVersion:
|
||||||
|
* type: integer
|
||||||
|
* description: Current database schema version
|
||||||
* 401:
|
* 401:
|
||||||
* description: Unauthorized — still includes `googleClientId` for the frontend
|
* description: Unauthorized — still includes `googleClientId` and `schemaVersion` for the frontend
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
@@ -44,12 +47,15 @@ export function register(app, basePath = '') {
|
|||||||
* type: string
|
* type: string
|
||||||
* googleClientId:
|
* googleClientId:
|
||||||
* type: string
|
* type: string
|
||||||
|
* schemaVersion:
|
||||||
|
* type: integer
|
||||||
|
* description: Current database schema version
|
||||||
*/
|
*/
|
||||||
app.get(`${basePath}/auth/me`, async (req, res) => {
|
app.get(`${basePath}/auth/me`, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const row = await db.getById(req.user.id);
|
const row = await db.getById(req.user.id);
|
||||||
if (!row) {
|
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);
|
const pictureBinary = toPictureBuffer(row.pictureBinary);
|
||||||
@@ -59,10 +65,10 @@ export function register(app, basePath = '') {
|
|||||||
selectedCompanyId: row.selectedCompanyId,
|
selectedCompanyId: row.selectedCompanyId,
|
||||||
...(pictureBinary && { picture: pictureBinary.toString('base64') }),
|
...(pictureBinary && { picture: pictureBinary.toString('base64') }),
|
||||||
};
|
};
|
||||||
respondWithAuthenticatedUser(res, 200, { user });
|
await respondWithAuthenticatedUser(res, 200, { user });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Get current user failed:', 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' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const schemaLog = createLogger('schema');
|
|||||||
let provider;
|
let provider;
|
||||||
let store;
|
let store;
|
||||||
let connecting;
|
let connecting;
|
||||||
|
let schemaVersion;
|
||||||
|
|
||||||
const STORE_NAMESPACES = [
|
const STORE_NAMESPACES = [
|
||||||
'companyuser',
|
'companyuser',
|
||||||
@@ -149,6 +150,7 @@ export async function getStore() {
|
|||||||
provider = createProvider();
|
provider = createProvider();
|
||||||
try {
|
try {
|
||||||
await provider.initialize((msg) => schemaLog.info(msg));
|
await provider.initialize((msg) => schemaLog.info(msg));
|
||||||
|
schemaVersion = await provider.getSchemaVersion();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
schemaLog.error('Database schema initialization failed:', err);
|
schemaLog.error('Database schema initialization failed:', err);
|
||||||
throw err;
|
throw err;
|
||||||
@@ -168,6 +170,19 @@ export async function getProvider() {
|
|||||||
return provider;
|
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() {
|
export async function ensureConnection() {
|
||||||
await getStore();
|
await getStore();
|
||||||
}
|
}
|
||||||
@@ -177,6 +192,7 @@ export async function closeStore() {
|
|||||||
await provider.close();
|
await provider.close();
|
||||||
provider = undefined;
|
provider = undefined;
|
||||||
store = undefined;
|
store = undefined;
|
||||||
|
schemaVersion = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user