docs
This commit is contained in:
93
README.md
93
README.md
@@ -1,89 +1,32 @@
|
|||||||
# Dashback API
|
# dashback
|
||||||
|
|
||||||
Backend API for the Dashback frontend. Default base URL:
|
Backend API for Dash — Google SSO auth, SQLite allowlist, and session cookies.
|
||||||
|
|
||||||
```
|
## Setup
|
||||||
http://localhost:4208/api
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
## CORS & cookies
|
Configure `.env` (see `.env.example`). Required for auth: `GOOGLE_CLIENT_ID`, `JWT_SECRET`, and `CORS_ORIGINS`.
|
||||||
|
|
||||||
All browser requests that need auth must send cookies:
|
## Run
|
||||||
|
|
||||||
```js
|
```bash
|
||||||
fetch('http://localhost:4208/api/auth/me', {
|
npm start
|
||||||
credentials: 'include',
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Sessions use an HTTP-only `token` cookie (`Secure`, `SameSite=Strict`, 24h). The browser must talk to the API over **HTTPS** for login to work — `Secure` cookies are not sent on plain HTTP.
|
Default port: `4208`.
|
||||||
|
|
||||||
## Auth flow
|
## API
|
||||||
|
|
||||||
1. Call `GET /auth/me` to get `googleClientId` (included in 200 and 401 responses).
|
See [docs/api](docs/api/README.md) for endpoints, auth flow, and frontend integration.
|
||||||
2. Sign in with Google on the frontend (Google Identity Services / One Tap).
|
|
||||||
3. POST the Google ID token to the API.
|
|
||||||
4. The API sets the session cookie.
|
|
||||||
5. Call protected routes with `credentials: 'include'`. The cookie is refreshed automatically when it has less than 2 hours left.
|
|
||||||
|
|
||||||
### `POST /auth/login`
|
## Users
|
||||||
|
|
||||||
Public. No cookie required.
|
Manage the email allowlist:
|
||||||
|
|
||||||
**Body (JSON)**
|
```bash
|
||||||
|
npm run manage-users
|
||||||
```json
|
|
||||||
{ "idToken": "<google-id-token>" }
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**200**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Login successful",
|
|
||||||
"user": { "id": 1, "email": "you@example.com", "picture": "https://lh3.googleusercontent.com/..." }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Errors**
|
|
||||||
|
|
||||||
| Status | Body |
|
|
||||||
|--------|------|
|
|
||||||
| 400 | `{ "error": "idToken is required" }` |
|
|
||||||
| 401 | `{ "error": "Invalid Google token" }` or `{ "error": "Invalid Google account" }` |
|
|
||||||
| 403 | `{ "error": "Email is not authorized" }` |
|
|
||||||
|
|
||||||
### `POST /auth/logout`
|
|
||||||
|
|
||||||
Public. Clears the session cookie.
|
|
||||||
|
|
||||||
**200**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{ "message": "Logged out successfully" }
|
|
||||||
```
|
|
||||||
|
|
||||||
### `GET /auth/me`
|
|
||||||
|
|
||||||
Protected. Requires a valid session cookie. Every response includes `googleClientId`.
|
|
||||||
|
|
||||||
**200**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"user": { "id": 1, "email": "you@example.com", "picture": "https://lh3.googleusercontent.com/..." },
|
|
||||||
"googleClientId": "your-client-id.apps.googleusercontent.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Errors**
|
|
||||||
|
|
||||||
| Status | Body |
|
|
||||||
|--------|------|
|
|
||||||
| 401 | `{ "error": "Unauthorized", "googleClientId": "..." }` or `{ "error": "Invalid token", "googleClientId": "..." }` |
|
|
||||||
| 403 | `{ "error": "User is no longer authorized", "googleClientId": "..." }` |
|
|
||||||
|
|
||||||
## Frontend checklist
|
|
||||||
|
|
||||||
- [ ] `credentials: 'include'` on every API call
|
|
||||||
- [ ] HTTPS in dev (or proxy) so `Secure` cookies work
|
|
||||||
|
|||||||
41
docs/api/README.md
Normal file
41
docs/api/README.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Dash API
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost:4208/api
|
||||||
|
```
|
||||||
|
|
||||||
|
## CORS & cookies
|
||||||
|
|
||||||
|
All browser requests that need auth must send cookies:
|
||||||
|
|
||||||
|
```js
|
||||||
|
fetch('http://localhost:4208/api/auth/me', {
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Sessions use an HTTP-only `token` cookie (`Secure`, `SameSite=Strict`, 24h). The browser must talk to the API over **HTTPS** for login to work — `Secure` cookies are not sent on plain HTTP.
|
||||||
|
|
||||||
|
## Auth flow
|
||||||
|
|
||||||
|
1. Call [`GET /auth/me`](routes/auth/me.md) to get `googleClientId` (included in 200 and 401 responses).
|
||||||
|
2. Sign in with Google on the frontend (Google Identity Services / One Tap).
|
||||||
|
3. POST the Google ID token to [`POST /auth/login`](routes/auth/login.md).
|
||||||
|
4. The API sets the session cookie.
|
||||||
|
5. Call protected routes with `credentials: 'include'`. The cookie is refreshed automatically when it has less than 2 hours left.
|
||||||
|
|
||||||
|
## Routes
|
||||||
|
|
||||||
|
Mirrors [`src/api/routes/`](../../src/api/routes/).
|
||||||
|
|
||||||
|
| File | Endpoint |
|
||||||
|
|------|----------|
|
||||||
|
| [`routes/auth/login.md`](routes/auth/login.md) | `POST /auth/login` |
|
||||||
|
| [`routes/auth/logout.md`](routes/auth/logout.md) | `POST /auth/logout` |
|
||||||
|
| [`routes/auth/me.md`](routes/auth/me.md) | `GET /auth/me` |
|
||||||
|
| [`routes/health.md`](routes/health.md) | `GET /health` |
|
||||||
|
|
||||||
|
## Frontend checklist
|
||||||
|
|
||||||
|
- [ ] `credentials: 'include'` on every API call
|
||||||
|
- [ ] HTTPS in dev (or proxy) so `Secure` cookies work
|
||||||
26
docs/api/routes/auth/login.md
Normal file
26
docs/api/routes/auth/login.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# `POST /auth/login`
|
||||||
|
|
||||||
|
Public. No cookie required.
|
||||||
|
|
||||||
|
**Body (JSON)**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "idToken": "<google-id-token>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
**200**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Login successful",
|
||||||
|
"user": { "id": 1, "email": "you@example.com", "picture": "https://lh3.googleusercontent.com/..." }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Errors**
|
||||||
|
|
||||||
|
| Status | Body |
|
||||||
|
|--------|------|
|
||||||
|
| 400 | `{ "error": "idToken is required" }` or `{ "error": "GOOGLE_CLIENT_ID is not configured" }` |
|
||||||
|
| 401 | `{ "error": "Invalid Google token" }` or `{ "error": "Invalid Google account" }` |
|
||||||
|
| 403 | `{ "error": "Email is not authorized" }` |
|
||||||
9
docs/api/routes/auth/logout.md
Normal file
9
docs/api/routes/auth/logout.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# `POST /auth/logout`
|
||||||
|
|
||||||
|
Public. Clears the session cookie.
|
||||||
|
|
||||||
|
**200**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "message": "Logged out successfully" }
|
||||||
|
```
|
||||||
19
docs/api/routes/auth/me.md
Normal file
19
docs/api/routes/auth/me.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# `GET /auth/me`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie. Every response includes `googleClientId`.
|
||||||
|
|
||||||
|
**200**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"user": { "id": 1, "email": "you@example.com", "picture": "https://lh3.googleusercontent.com/..." },
|
||||||
|
"googleClientId": "your-client-id.apps.googleusercontent.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Errors**
|
||||||
|
|
||||||
|
| Status | Body |
|
||||||
|
|--------|------|
|
||||||
|
| 401 | `{ "error": "Unauthorized", "googleClientId": "..." }` or `{ "error": "Invalid token", "googleClientId": "..." }` |
|
||||||
|
| 403 | `{ "error": "User is no longer authorized", "googleClientId": "..." }` |
|
||||||
15
docs/api/routes/health.md
Normal file
15
docs/api/routes/health.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# `GET /health`
|
||||||
|
|
||||||
|
Public. No cookie required.
|
||||||
|
|
||||||
|
**200**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "ok": true }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Errors**
|
||||||
|
|
||||||
|
| Status | Body |
|
||||||
|
|--------|------|
|
||||||
|
| 503 | `{ "ok": false, "error": "..." }` |
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import env from '../../../config/env.js';
|
import env from '../../config/env.js';
|
||||||
|
|
||||||
export function meJson(res, status, body) {
|
export function meJson(res, status, body) {
|
||||||
return res.status(status).json({ ...body, googleClientId: env.googleClientId });
|
return res.status(status).json({ ...body, googleClientId: env.googleClientId });
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
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 { meJson } from '../routes/auth/meJson.js';
|
import { meJson } from '../auth/meJson.js';
|
||||||
|
|
||||||
const userCache = new Map();
|
const userCache = new Map();
|
||||||
const USER_CACHE_TTL = 10 * 60 * 1000;
|
const USER_CACHE_TTL = 10 * 60 * 1000;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { meJson } from './meJson.js';
|
import { meJson } from '../../auth/meJson.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the current-user route (requires prior auth middleware populating `req.user`).
|
* Registers the current-user route (requires prior auth middleware populating `req.user`).
|
||||||
|
|||||||
Reference in New Issue
Block a user