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) */
|
||||
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');
|
||||
});
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
}
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user