From 629ff78af8038cad352f7f19b1ba5740d44b470b Mon Sep 17 00:00:00 2001 From: Thomas Faour Date: Sun, 10 Aug 2025 13:21:21 -0400 Subject: [PATCH] gitea workflows --- .gitea/workflows/docker-build.yml | 59 +++++++++++++++++++++++++++++++ backend/src/server.ts | 55 +++++++++++++++++++++++----- docker-compose.yml | 39 +++++++++++++------- frontend/Dockerfile | 6 ++++ frontend/vite.config.ts | 3 +- 5 files changed, 140 insertions(+), 22 deletions(-) create mode 100644 .gitea/workflows/docker-build.yml diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml new file mode 100644 index 0000000..6f72fff --- /dev/null +++ b/.gitea/workflows/docker-build.yml @@ -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 diff --git a/backend/src/server.ts b/backend/src/server.ts index a9fecce..9941fac 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -22,16 +22,36 @@ app.use(session({ })); /* 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; async function getClient() { if (oidcClient) return oidcClient; - const issuerUrl = process.env.OIDC_ISSUER; - if (!issuerUrl) throw new Error('OIDC_ISSUER missing'); - const issuer = await Issuer.discover(issuerUrl); + const issuer = await Issuer.discover(oidcCfg.issuer); oidcClient = new issuer.Client({ - client_id: process.env.OIDC_CLIENT_ID!, - client_secret: process.env.OIDC_CLIENT_SECRET!, - redirect_uris: [process.env.OIDC_REDIRECT_URI!], + client_id: oidcCfg.clientId, + client_secret: oidcCfg.clientSecret, + redirect_uris: [oidcCfg.redirectUri], response_types: ['code'] }); return oidcClient; @@ -61,7 +81,7 @@ app.get('/api/auth/callback', async (req,res)=>{ const params = client.callbackParams(req); const saved = (req.session as any).oidc; 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 user = upsertUser(claims.sub, claims.email); (req.session as any).userId = user.id; @@ -149,5 +169,24 @@ app.post('/api/sync/:accountId', requireAuth, (req,res)=>{ 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); -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'); +}); diff --git a/docker-compose.yml b/docker-compose.yml index 0b235ab..d46605b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,32 +1,45 @@ version: "3.9" services: backend: + image: ${REGISTRY:-registry.local}/${IMAGE_NAMESPACE:-youruser}/imap-client-backend:latest build: context: ./backend dockerfile: Dockerfile + # You can also add: env_file: .env (uncomment if you create a .env) environment: - - IMAP_CLIENT_DB_PATH=/data/app.db - - NODE_ENV=production - - PORT=8080 - - OIDC_ISSUER=https://auth.thumeit.com/.well-known/openid-configuration - - OIDC_CLIENT_ID=changeme - - OIDC_CLIENT_SECRET=changeme - - OIDC_REDIRECT_URI=http://localhost:8080/api/auth/callback - - SESSION_SECRET=dev_change_me - - FRONTEND_ORIGIN=http://localhost:5173 - - OIDC_PROVIDER=AuthServer - # Optional: BING_MKT=en-US (frontend will default to en-US if unset) + IMAP_CLIENT_DB_PATH: /data/app.db + NODE_ENV: production + PORT: 8080 + # OIDC (override in .env for production) + OIDC_ISSUER: ${OIDC_ISSUER:-https://auth.thumeit.com/.well-known/openid-configuration} + OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-imap_client} + OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-replace-with-real-secret} + OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI:-http://localhost:8080/api/auth/callback} + OIDC_PROVIDER: ${OIDC_PROVIDER:-AuthServer} + # Session secret (generate: openssl rand -base64 48) + 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: - backend_data:/data ports: - "8080:8080" frontend: + image: ${REGISTRY:-registry.local}/${IMAGE_NAMESPACE:-youruser}/imap-client-frontend:latest build: context: ./frontend 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: - - VITE_API_BASE=http://localhost:8080 - - OIDC_PROVIDER=AuthServer + 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} ports: - "5173:80" depends_on: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 819c401..cecae15 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -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 src ./src ARG VITE_API_BASE +ARG OIDC_PROVIDER +ARG VITE_BING_MKT +ARG VITE_BING_DISABLE 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 FROM nginx:1.27-alpine diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index dfc69c8..e241aee 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -5,6 +5,7 @@ export default defineConfig({ plugins:[react()], define: { __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') } });