diff --git a/docs/api/routes/company/create.md b/docs/api/routes/company/create.md index 5891499..0e4a5bb 100644 --- a/docs/api/routes/company/create.md +++ b/docs/api/routes/company/create.md @@ -60,6 +60,7 @@ Created; selected company updated |--------|-------------| | 400 | Validation error — `{"error":"…"}` | | 401 | Unauthorized — `{"error":"…"}` | +| 403 | Not authorized (invited users cannot create companies when implicit create is disabled) — `{"error":"…"}` | --- diff --git a/src/api/routes/auth/login.js b/src/api/routes/auth/login.js index 3e444c6..643f7c4 100644 --- a/src/api/routes/auth/login.js +++ b/src/api/routes/auth/login.js @@ -3,6 +3,7 @@ import { OAuth2Client } from 'google-auth-library'; import env from '../../../config/env.js'; import * as db from '../../../db/googleAuth.js'; import * as autoinviteDb from '../../../db/autoinvite.js'; +import * as companyDb from '../../../db/company.js'; import { clearCachedUser } from '../../middleware/auth.js'; import { downloadPicture, toPictureBuffer } from '../../../util/pictureBinary.js'; @@ -109,6 +110,13 @@ export function register(app, basePath = '') { if (!allowed && (env.implicitCreate || (await autoinviteDb.hasPendingInvite(email)))) { await db.addAllowedEmail(email); allowed = await db.getAllowedEmail(email); + if (allowed && allowed.selectedCompanyId == null) { + const companies = await companyDb.listCompanies(allowed.id); + if (companies.length > 0) { + await db.setSelectedCompany(allowed.id, companies[0].id); + allowed = await db.getAllowedEmail(email); + } + } } if (!allowed) { console.warn(`Login rejected: email not authorized (${email})`); diff --git a/src/api/routes/company.js b/src/api/routes/company.js index 21fa4de..8241ffb 100644 --- a/src/api/routes/company.js +++ b/src/api/routes/company.js @@ -2,6 +2,7 @@ import fs from 'fs'; import path from 'path'; import multer from 'multer'; import JSZip from 'jszip'; +import env from '../../config/env.js'; import * as db from '../../db/company.js'; import * as googleAuth from '../../db/googleAuth.js'; import { clearCachedUser } from '../middleware/auth.js'; @@ -87,7 +88,7 @@ function companyErrorStatus(error) { if (error.message === 'Company not found') return 404; if (error.message === 'Company name not available') return 409; if (error.message === 'Invalid tax rate' || error.message === 'Text required' || error.message === 'No company selected') return 400; - if (error.message === 'Not authorized') return 403; + if (error.message === 'Not authorized' || error.message === 'Invited users cannot create a company') return 403; return 500; } @@ -186,6 +187,12 @@ export function register(app, basePath = '') { * application/json: * schema: * $ref: '#/components/schemas/Error' + * 403: + * description: Not authorized (invited users cannot create companies when implicit create is disabled) + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' */ // #region GET /company @@ -209,6 +216,13 @@ export function register(app, basePath = '') { return res.status(400).json({ error: body.error }); } + if (!env.implicitCreate) { + const isInvited = await db.isInvitedUser(req.user.id); + if (isInvited) { + return res.status(403).json({ error: 'Invited users cannot create a company' }); + } + } + try { const company = await db.createCompany(req.user.id, body); await googleAuth.setSelectedCompany(req.user.id, company.id); diff --git a/src/db/company.js b/src/db/company.js index 935802d..775fb94 100644 --- a/src/db/company.js +++ b/src/db/company.js @@ -1,4 +1,4 @@ -import { getStore } from './store.js'; +import { getStore, getProvider } from './store.js'; export async function listCompanies(authId) { const store = await getStore(); @@ -34,3 +34,31 @@ export async function addCompanyHistory(googleAuthId, data) { const store = await getStore(); return store.company.addHistory(googleAuthId, data); } + +export async function isInvitedUser(googleAuthId) { + const provider = await getProvider(); + const isSqlite = provider?.dialect === 'sqlite3'; + const sql = isSqlite + ? `SELECT 1 AS is_creator + FROM companyuser cu + WHERE cu.google_auth_id = @googleAuthId + AND cu.id = (SELECT MIN(cu2.id) FROM companyuser cu2 WHERE cu2.company_id = cu.company_id) + LIMIT 1` + : `SELECT TOP 1 1 AS is_creator + FROM companyuser cu + WHERE cu.google_auth_id = @googleAuthId + AND cu.id = (SELECT MIN(cu2.id) FROM companyuser cu2 WHERE cu2.company_id = cu.company_id)`; + + const creatorResult = await provider.query(sql, { googleAuthId }); + if (creatorResult.rows.length > 0) { + return false; + } + + const memberResult = await provider.query( + isSqlite + ? `SELECT 1 AS is_member FROM companyuser WHERE google_auth_id = @googleAuthId LIMIT 1` + : `SELECT TOP 1 1 AS is_member FROM companyuser WHERE google_auth_id = @googleAuthId`, + { googleAuthId } + ); + return memberResult.rows.length > 0; +}