u
This commit is contained in:
@@ -17,6 +17,7 @@ CORS_ORIGINS=http://localhost:5173
|
|||||||
# Auth — Google OAuth client ID and JWT signing secret
|
# Auth — Google OAuth client ID and JWT signing secret
|
||||||
GOOGLE_CLIENT_ID=
|
GOOGLE_CLIENT_ID=
|
||||||
JWT_SECRET=fallback_secret_please_change_in_production
|
JWT_SECRET=fallback_secret_please_change_in_production
|
||||||
|
# Auto-create user on first login (when false, only allowlisted or invited emails can sign in / create an account)
|
||||||
IMPLICIT_CREATE=false
|
IMPLICIT_CREATE=false
|
||||||
|
|
||||||
# Database Provider — 'sqlite' or 'mssql' (defaults to 'mssql', or 'sqlite' if SQLITE_PATH is set)
|
# Database Provider — 'sqlite' or 'mssql' (defaults to 'mssql', or 'sqlite' if SQLITE_PATH is set)
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ Public. No cookie required.
|
|||||||
|
|
||||||
Login with Google ID token
|
Login with Google ID token
|
||||||
|
|
||||||
Verifies a Google ID token, checks allowlist (or creates entry when `IMPLICIT_CREATE=true`),
|
Verifies a Google ID token, checks allowlist (or creates entry when `IMPLICIT_CREATE=true`
|
||||||
|
or when the email has a pending auto-invitation),
|
||||||
stores Google `sub` and profile image bytes, and sets an HTTP-only `token` cookie.
|
stores Google `sub` and profile image bytes, and sets an HTTP-only `token` cookie.
|
||||||
`user.picture` is the profile image as base64 (same as GET /auth/me).
|
`user.picture` is the profile image as base64 (same as GET /auth/me).
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ Login successful; `token` cookie set
|
|||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| 400 | Missing token or Google client not configured — `{"error":"…"}` |
|
| 400 | Missing token or Google client not configured — `{"error":"…"}` |
|
||||||
| 401 | Invalid Google token or account — `{"error":"…"}` |
|
| 401 | Invalid Google token or account — `{"error":"…"}` |
|
||||||
| 403 | Email not on allowlist — `{"error":"…"}` |
|
| 403 | Email not on allowlist or invited — `{"error":"…"}` |
|
||||||
| 500 | Server error — `{"error":"…"}` |
|
| 500 | Server error — `{"error":"…"}` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import jwt from 'jsonwebtoken';
|
|||||||
import { OAuth2Client } from 'google-auth-library';
|
import { OAuth2Client } from 'google-auth-library';
|
||||||
import env from '../../../config/env.js';
|
import env from '../../../config/env.js';
|
||||||
import * as db from '../../../db/googleAuth.js';
|
import * as db from '../../../db/googleAuth.js';
|
||||||
|
import * as autoinviteDb from '../../../db/autoinvite.js';
|
||||||
import { clearCachedUser } from '../../middleware/auth.js';
|
import { clearCachedUser } from '../../middleware/auth.js';
|
||||||
import { downloadPicture, toPictureBuffer } from '../../../util/pictureBinary.js';
|
import { downloadPicture, toPictureBuffer } from '../../../util/pictureBinary.js';
|
||||||
|
|
||||||
@@ -20,7 +21,8 @@ export function register(app, basePath = '') {
|
|||||||
* tags: [Auth]
|
* tags: [Auth]
|
||||||
* summary: Login with Google ID token
|
* summary: Login with Google ID token
|
||||||
* description: |
|
* description: |
|
||||||
* Verifies a Google ID token, checks allowlist (or creates entry when `IMPLICIT_CREATE=true`),
|
* Verifies a Google ID token, checks allowlist (or creates entry when `IMPLICIT_CREATE=true`
|
||||||
|
* or when the email has a pending auto-invitation),
|
||||||
* stores Google `sub` and profile image bytes, and sets an HTTP-only `token` cookie.
|
* stores Google `sub` and profile image bytes, and sets an HTTP-only `token` cookie.
|
||||||
* `user.picture` is the profile image as base64 (same as GET /auth/me).
|
* `user.picture` is the profile image as base64 (same as GET /auth/me).
|
||||||
* security: []
|
* security: []
|
||||||
@@ -64,7 +66,7 @@ export function register(app, basePath = '') {
|
|||||||
* schema:
|
* schema:
|
||||||
* $ref: '#/components/schemas/Error'
|
* $ref: '#/components/schemas/Error'
|
||||||
* 403:
|
* 403:
|
||||||
* description: Email not on allowlist
|
* description: Email not on allowlist or invited
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
@@ -104,7 +106,7 @@ export function register(app, basePath = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let allowed = await db.getAllowedEmail(email);
|
let allowed = await db.getAllowedEmail(email);
|
||||||
if (!allowed && env.implicitCreate) {
|
if (!allowed && (env.implicitCreate || (await autoinviteDb.hasPendingInvite(email)))) {
|
||||||
await db.addAllowedEmail(email);
|
await db.addAllowedEmail(email);
|
||||||
allowed = await db.getAllowedEmail(email);
|
allowed = await db.getAllowedEmail(email);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getStore } from './store.js';
|
import { getStore, getProvider } from './store.js';
|
||||||
|
|
||||||
export async function list(googleAuthId) {
|
export async function list(googleAuthId) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
@@ -14,3 +14,14 @@ export async function remove(googleAuthId, id) {
|
|||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.autoinvite.remove(googleAuthId, id);
|
return store.autoinvite.remove(googleAuthId, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function hasPendingInvite(email) {
|
||||||
|
if (!email || typeof email !== 'string') return false;
|
||||||
|
const normalized = email.trim().toLowerCase();
|
||||||
|
const provider = await getProvider();
|
||||||
|
const result = await provider.query(
|
||||||
|
'SELECT 1 AS found FROM autoinvite WHERE email = @email',
|
||||||
|
{ email: normalized }
|
||||||
|
);
|
||||||
|
return result.rows.length > 0;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user