This commit is contained in:
parent
507de383cf
commit
629ff78af8
59
.gitea/workflows/docker-build.yml
Normal file
59
.gitea/workflows/docker-build.yml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
name: Build & Push Docker Images
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main", "master"]
|
||||||
|
tags: ["v*.*.*"]
|
||||||
|
pull_request:
|
||||||
|
branches: ["main", "master"]
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ${REGISTRY:-registry.local} # Override via repository/organization secret or .env in runner
|
||||||
|
IMAGE_NAMESPACE: ${IMAGE_NAMESPACE:-youruser} # Your Gitea username/org
|
||||||
|
BACKEND_IMAGE: imap-client-backend
|
||||||
|
FRONTEND_IMAGE: imap-client-frontend
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: docker
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Prepare tags
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
SHA_TAG=sha-${GITHUB_SHA::7}
|
||||||
|
echo "sha_tag=$SHA_TAG" >> $GITHUB_OUTPUT
|
||||||
|
if [[ "$GITHUB_REF" == refs/heads/main || "$GITHUB_REF" == refs/heads/master ]]; then
|
||||||
|
echo "latest_tag=latest" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
|
||||||
|
VERSION_TAG=${GITHUB_REF#refs/tags/}
|
||||||
|
echo "version_tag=$VERSION_TAG" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Login to registry
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.CR_PASSWORD }}" | docker login "${REGISTRY}" -u "${{ secrets.CR_USERNAME }}" --password-stdin
|
||||||
|
|
||||||
|
- name: Build backend
|
||||||
|
run: |
|
||||||
|
docker build -t ${REGISTRY}/${IMAGE_NAMESPACE}/${BACKEND_IMAGE}:${{ steps.meta.outputs.sha_tag }} ./backend
|
||||||
|
if [ -n "${{ steps.meta.outputs.latest_tag }}" ]; then docker tag ${REGISTRY}/${IMAGE_NAMESPACE}/${BACKEND_IMAGE}:${{ steps.meta.outputs.sha_tag }} ${REGISTRY}/${IMAGE_NAMESPACE}/${BACKEND_IMAGE}:latest; fi
|
||||||
|
if [ -n "${{ steps.meta.outputs.version_tag }}" ]; then docker tag ${REGISTRY}/${IMAGE_NAMESPACE}/${BACKEND_IMAGE}:${{ steps.meta.outputs.sha_tag }} ${REGISTRY}/${IMAGE_NAMESPACE}/${BACKEND_IMAGE}:${{ steps.meta.outputs.version_tag }}; fi
|
||||||
|
|
||||||
|
- name: Build frontend
|
||||||
|
run: |
|
||||||
|
docker build -t ${REGISTRY}/${IMAGE_NAMESPACE}/${FRONTEND_IMAGE}:${{ steps.meta.outputs.sha_tag }} ./frontend
|
||||||
|
if [ -n "${{ steps.meta.outputs.latest_tag }}" ]; then docker tag ${REGISTRY}/${IMAGE_NAMESPACE}/${FRONTEND_IMAGE}:${{ steps.meta.outputs.sha_tag }} ${REGISTRY}/${IMAGE_NAMESPACE}/${FRONTEND_IMAGE}:latest; fi
|
||||||
|
if [ -n "${{ steps.meta.outputs.version_tag }}" ]; then docker tag ${REGISTRY}/${IMAGE_NAMESPACE}/${FRONTEND_IMAGE}:${{ steps.meta.outputs.sha_tag }} ${REGISTRY}/${IMAGE_NAMESPACE}/${FRONTEND_IMAGE}:${{ steps.meta.outputs.version_tag }}; fi
|
||||||
|
|
||||||
|
- name: Push images
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
run: |
|
||||||
|
for IMG in ${BACKEND_IMAGE} ${FRONTEND_IMAGE}; do
|
||||||
|
docker push ${REGISTRY}/${IMAGE_NAMESPACE}/${IMG}:${{ steps.meta.outputs.sha_tag }}
|
||||||
|
if [ -n "${{ steps.meta.outputs.latest_tag }}" ]; then docker push ${REGISTRY}/${IMAGE_NAMESPACE}/${IMG}:latest; fi
|
||||||
|
if [ -n "${{ steps.meta.outputs.version_tag }}" ]; then docker push ${REGISTRY}/${IMAGE_NAMESPACE}/${IMG}:${{ steps.meta.outputs.version_tag }}; fi
|
||||||
|
done
|
@ -22,16 +22,36 @@ app.use(session({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
/* OIDC setup (lazy) */
|
/* OIDC setup (lazy) */
|
||||||
|
type OidcConfig = {
|
||||||
|
issuer:string;
|
||||||
|
clientId:string;
|
||||||
|
clientSecret:string;
|
||||||
|
redirectUri:string;
|
||||||
|
providerName:string;
|
||||||
|
};
|
||||||
|
function loadOidcConfig():OidcConfig {
|
||||||
|
const issuer = process.env.OIDC_ISSUER || '';
|
||||||
|
const clientId = process.env.OIDC_CLIENT_ID || '';
|
||||||
|
const clientSecret = process.env.OIDC_CLIENT_SECRET || '';
|
||||||
|
const redirectUri = process.env.OIDC_REDIRECT_URI || '';
|
||||||
|
const providerName = process.env.OIDC_PROVIDER || 'Identity Provider';
|
||||||
|
if (!issuer || !clientId || !clientSecret || !redirectUri) {
|
||||||
|
throw new Error('OIDC env vars incomplete (OIDC_ISSUER / OIDC_CLIENT_ID / OIDC_CLIENT_SECRET / OIDC_REDIRECT_URI)');
|
||||||
|
}
|
||||||
|
if (clientSecret.includes('replace-with-real-secret')) {
|
||||||
|
log.warn('Using placeholder OIDC_CLIENT_SECRET – replace before production.');
|
||||||
|
}
|
||||||
|
return { issuer, clientId, clientSecret, redirectUri, providerName };
|
||||||
|
}
|
||||||
|
const oidcCfg = loadOidcConfig();
|
||||||
let oidcClient: Client | null = null;
|
let oidcClient: Client | null = null;
|
||||||
async function getClient() {
|
async function getClient() {
|
||||||
if (oidcClient) return oidcClient;
|
if (oidcClient) return oidcClient;
|
||||||
const issuerUrl = process.env.OIDC_ISSUER;
|
const issuer = await Issuer.discover(oidcCfg.issuer);
|
||||||
if (!issuerUrl) throw new Error('OIDC_ISSUER missing');
|
|
||||||
const issuer = await Issuer.discover(issuerUrl);
|
|
||||||
oidcClient = new issuer.Client({
|
oidcClient = new issuer.Client({
|
||||||
client_id: process.env.OIDC_CLIENT_ID!,
|
client_id: oidcCfg.clientId,
|
||||||
client_secret: process.env.OIDC_CLIENT_SECRET!,
|
client_secret: oidcCfg.clientSecret,
|
||||||
redirect_uris: [process.env.OIDC_REDIRECT_URI!],
|
redirect_uris: [oidcCfg.redirectUri],
|
||||||
response_types: ['code']
|
response_types: ['code']
|
||||||
});
|
});
|
||||||
return oidcClient;
|
return oidcClient;
|
||||||
@ -61,7 +81,7 @@ app.get('/api/auth/callback', async (req,res)=>{
|
|||||||
const params = client.callbackParams(req);
|
const params = client.callbackParams(req);
|
||||||
const saved = (req.session as any).oidc;
|
const saved = (req.session as any).oidc;
|
||||||
if (!saved || params.state !== saved.state) return res.status(400).send('Bad state');
|
if (!saved || params.state !== saved.state) return res.status(400).send('Bad state');
|
||||||
const tokenSet = await client.callback(process.env.OIDC_REDIRECT_URI!, params, { state: saved.state, nonce: saved.nonce });
|
const tokenSet = await client.callback(oidcCfg.redirectUri, params, { state: saved.state, nonce: saved.nonce });
|
||||||
const claims = tokenSet.claims();
|
const claims = tokenSet.claims();
|
||||||
const user = upsertUser(claims.sub, claims.email);
|
const user = upsertUser(claims.sub, claims.email);
|
||||||
(req.session as any).userId = user.id;
|
(req.session as any).userId = user.id;
|
||||||
@ -149,5 +169,24 @@ app.post('/api/sync/:accountId', requireAuth, (req,res)=>{
|
|||||||
res.json({ started:true });
|
res.json({ started:true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/api/auth/config', (_req,res)=>{
|
||||||
|
res.json({
|
||||||
|
issuer: oidcCfg.issuer,
|
||||||
|
redirectUri: oidcCfg.redirectUri,
|
||||||
|
provider: oidcCfg.providerName,
|
||||||
|
clientIdPreview: oidcCfg.clientId ? oidcCfg.clientId.slice(0,4)+'...' : ''
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const port = Number(process.env.PORT||8080);
|
const port = Number(process.env.PORT||8080);
|
||||||
app.listen(port, ()=> log.info({port}, 'listening'));
|
app.listen(port, ()=> {
|
||||||
|
log.info({
|
||||||
|
oidc: {
|
||||||
|
issuer: oidcCfg.issuer,
|
||||||
|
redirect: oidcCfg.redirectUri,
|
||||||
|
provider: oidcCfg.providerName,
|
||||||
|
clientIdLen: oidcCfg.clientId.length
|
||||||
|
},
|
||||||
|
port
|
||||||
|
}, 'listening');
|
||||||
|
});
|
||||||
|
@ -1,32 +1,45 @@
|
|||||||
version: "3.9"
|
version: "3.9"
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
|
image: ${REGISTRY:-registry.local}/${IMAGE_NAMESPACE:-youruser}/imap-client-backend:latest
|
||||||
build:
|
build:
|
||||||
context: ./backend
|
context: ./backend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
# You can also add: env_file: .env (uncomment if you create a .env)
|
||||||
environment:
|
environment:
|
||||||
- IMAP_CLIENT_DB_PATH=/data/app.db
|
IMAP_CLIENT_DB_PATH: /data/app.db
|
||||||
- NODE_ENV=production
|
NODE_ENV: production
|
||||||
- PORT=8080
|
PORT: 8080
|
||||||
- OIDC_ISSUER=https://auth.thumeit.com/.well-known/openid-configuration
|
# OIDC (override in .env for production)
|
||||||
- OIDC_CLIENT_ID=changeme
|
OIDC_ISSUER: ${OIDC_ISSUER:-https://auth.thumeit.com/.well-known/openid-configuration}
|
||||||
- OIDC_CLIENT_SECRET=changeme
|
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-imap_client}
|
||||||
- OIDC_REDIRECT_URI=http://localhost:8080/api/auth/callback
|
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-replace-with-real-secret}
|
||||||
- SESSION_SECRET=dev_change_me
|
OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI:-http://localhost:8080/api/auth/callback}
|
||||||
- FRONTEND_ORIGIN=http://localhost:5173
|
OIDC_PROVIDER: ${OIDC_PROVIDER:-AuthServer}
|
||||||
- OIDC_PROVIDER=AuthServer
|
# Session secret (generate: openssl rand -base64 48)
|
||||||
# Optional: BING_MKT=en-US (frontend will default to en-US if unset)
|
SESSION_SECRET: ${SESSION_SECRET:-change-this-session-secret}
|
||||||
|
# Frontend origin(s) comma-separated for CORS
|
||||||
|
FRONTEND_ORIGIN: ${FRONTEND_ORIGIN:-http://localhost:5173}
|
||||||
|
# Optional future: BING_MKT backend side (frontend uses VITE_BING_MKT)
|
||||||
volumes:
|
volumes:
|
||||||
- backend_data:/data
|
- backend_data:/data
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
frontend:
|
frontend:
|
||||||
|
image: ${REGISTRY:-registry.local}/${IMAGE_NAMESPACE:-youruser}/imap-client-frontend:latest
|
||||||
build:
|
build:
|
||||||
context: ./frontend
|
context: ./frontend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
VITE_API_BASE: ${VITE_API_BASE:-http://localhost:8080}
|
||||||
|
OIDC_PROVIDER: ${OIDC_PROVIDER:-AuthServer}
|
||||||
|
VITE_BING_MKT: ${VITE_BING_MKT:-en-US}
|
||||||
|
VITE_BING_DISABLE: ${VITE_BING_DISABLE:-0}
|
||||||
environment:
|
environment:
|
||||||
- VITE_API_BASE=http://localhost:8080
|
VITE_API_BASE: ${VITE_API_BASE:-http://localhost:8080}
|
||||||
- OIDC_PROVIDER=AuthServer
|
OIDC_PROVIDER: ${OIDC_PROVIDER:-AuthServer}
|
||||||
|
VITE_BING_MKT: ${VITE_BING_MKT:-en-US}
|
||||||
|
VITE_BING_DISABLE: ${VITE_BING_DISABLE:-0}
|
||||||
ports:
|
ports:
|
||||||
- "5173:80"
|
- "5173:80"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
@ -5,7 +5,13 @@ RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi && npm instal
|
|||||||
COPY index.html vite.config.ts tsconfig.json ./
|
COPY index.html vite.config.ts tsconfig.json ./
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
ARG VITE_API_BASE
|
ARG VITE_API_BASE
|
||||||
|
ARG OIDC_PROVIDER
|
||||||
|
ARG VITE_BING_MKT
|
||||||
|
ARG VITE_BING_DISABLE
|
||||||
ENV VITE_API_BASE=$VITE_API_BASE
|
ENV VITE_API_BASE=$VITE_API_BASE
|
||||||
|
ENV OIDC_PROVIDER=$OIDC_PROVIDER
|
||||||
|
ENV VITE_BING_MKT=$VITE_BING_MKT
|
||||||
|
ENV VITE_BING_DISABLE=$VITE_BING_DISABLE
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM nginx:1.27-alpine
|
FROM nginx:1.27-alpine
|
||||||
|
@ -5,6 +5,7 @@ export default defineConfig({
|
|||||||
plugins:[react()],
|
plugins:[react()],
|
||||||
define: {
|
define: {
|
||||||
__API_BASE__: JSON.stringify(process.env.VITE_API_BASE || 'http://localhost:8080'),
|
__API_BASE__: JSON.stringify(process.env.VITE_API_BASE || 'http://localhost:8080'),
|
||||||
__OIDC_PROVIDER__: JSON.stringify(process.env.OIDC_PROVIDER || process.env.VITE_OIDC_PROVIDER || 'Identity Provider')
|
__OIDC_PROVIDER__: JSON.stringify(process.env.OIDC_PROVIDER || process.env.VITE_OIDC_PROVIDER || 'Identity Provider'),
|
||||||
|
__PROVIDER_NAME__: JSON.stringify(process.env.PROVIDER_NAME || 'Default Provider')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user