Reduce image size, update feature requset yamls and fix OIDC

This commit is contained in:
LukeGus
2025-09-29 21:21:51 -05:00
parent a09fe0a271
commit 59094ec25d
10 changed files with 250 additions and 73 deletions

View File

@@ -51,6 +51,18 @@ repo-images/
# Uploads directory
uploads/
# Electron files (not needed for Docker)
electron/
electron-builder.json
# Development and build artifacts
*.log
*.tmp
*.temp
# Font files (we'll optimize these in Dockerfile)
# public/fonts/*.ttf
# Logs
logs
*.log

View File

@@ -1,32 +0,0 @@
---
name: Bug report
about: Create a report to help Termix improve
title: "[BUG]"
labels: bug
assignees: ""
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots/Logs**
If applicable, add screenshots or console/Docker logs to help explain your problem.
**Environment (please complete the following information):**
- Browser [e.g. chrome, safari]
- Version [e.g. 1.6.0]
**Additional context**
Add any other context about the problem here.

82
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,82 @@
name: Bug report
description: Create a report to help Termix improve
title: "[BUG]"
labels: [bug]
assignees: []
body:
- type: input
id: title
attributes:
label: Title
description: Brief, descriptive title for the bug
placeholder: "Brief description of the bug"
validations:
required: true
- type: dropdown
id: platform
attributes:
label: Platform
description: How are you using Termix?
options:
- Website - Firefox
- Website - Safari
- Website - Chrome
- Website - Other Browser
- App - Windows
- App - Linux
- App - iOS
- App - Android
validations:
required: true
- type: dropdown
id: server-installation-method
attributes:
label: Server Installation Method
description: How is the Termix server installed?
options:
- Docker
- Manual Build
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: Find your version in the User Profile tab
placeholder: "e.g., 1.6.0"
validations:
required: true
- type: checkboxes
id: troubleshooting
attributes:
label: Troubleshooting
description: Please check all that apply
options:
- label: I have examined logs and tried to find the issue
- label: I have reviewed opened and closed issues
- label: I have tried restarting the application
- type: textarea
id: problem-description
attributes:
label: The Problem
description: Describe the bug in detail. Include as much information as possible with screenshots if applicable.
placeholder: "Describe what went wrong..."
validations:
required: true
- type: textarea
id: reproduction-steps
attributes:
label: How to Reproduce
description: Use as few steps as possible to reproduce the issue
placeholder: |
1.
2.
3.
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Any other context about the problem
placeholder: "Add any other context about the problem here..."

View File

@@ -1,19 +0,0 @@
---
name: Feature request
about: Suggest an idea for Termix
title: "[FEATURE]"
labels: enhancement
assignees: ""
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,36 @@
name: Feature request
description: Suggest an idea for Termix
title: "[FEATURE]"
labels: [enhancement]
assignees: []
body:
- type: input
id: title
attributes:
label: Title
description: Brief, descriptive title for the feature request
placeholder: "Brief description of the feature"
validations:
required: true
- type: textarea
id: related-issue
attributes:
label: Is it related to an issue?
description: Describe the problem this feature would solve
placeholder: "Describe what problem this feature would solve..."
validations:
required: true
- type: textarea
id: solution
attributes:
label: The Solution
description: Describe your proposed solution in detail
placeholder: "Describe how you envision this feature working..."
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Any other context or screenshots about the feature request
placeholder: "Add any other context about the feature request here..."

View File

@@ -20,6 +20,8 @@ WORKDIR /app
COPY . .
RUN find public/fonts -name "*.ttf" ! -name "*Regular.ttf" ! -name "*Bold.ttf" ! -name "*Italic.ttf" -delete
RUN npm cache clean --force && \
npm run build
@@ -69,16 +71,14 @@ RUN apt-get update && apt-get install -y nginx gettext-base openssl && \
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/nginx-https.conf /etc/nginx/nginx-https.conf
COPY --from=frontend-builder /app/dist /usr/share/nginx/html
COPY --from=frontend-builder /app/src/locales /usr/share/nginx/html/locales
COPY --from=frontend-builder /app/public/fonts /usr/share/nginx/html/fonts
RUN chown -R nginx:nginx /usr/share/nginx/html
COPY --from=production-deps /app/node_modules /app/node_modules
COPY --from=backend-builder /app/dist/backend ./dist/backend
COPY --chown=nginx:nginx --from=frontend-builder /app/dist /usr/share/nginx/html
COPY --chown=nginx:nginx --from=frontend-builder /app/src/locales /usr/share/nginx/html/locales
COPY --chown=nginx:nginx --from=frontend-builder /app/public/fonts /usr/share/nginx/html/fonts
COPY package.json ./
RUN chown -R node:node /app
COPY --chown=node:node --from=production-deps /app/node_modules /app/node_modules
COPY --chown=node:node --from=backend-builder /app/dist/backend ./dist/backend
COPY --chown=node:node package.json ./
VOLUME ["/app/data"]

View File

@@ -75,12 +75,8 @@ async function verifyOIDCToken(
);
}
} else {
authLogger.error(
`JWKS fetch failed from ${url}: ${response.status} ${response.statusText}`,
);
}
} catch (error) {
authLogger.error(`JWKS fetch error from ${url}:`, error);
continue;
}
}
@@ -117,7 +113,6 @@ async function verifyOIDCToken(
return payload;
} catch (error) {
authLogger.error("OIDC token verification failed:", error);
throw error;
}
}
@@ -655,10 +650,6 @@ router.get("/oidc/callback", async (req, res) => {
config.client_id,
);
} catch (error) {
authLogger.error(
"OIDC token verification failed, trying userinfo endpoints",
error,
);
try {
const parts = tokenData.id_token.split(".");
if (parts.length === 3) {
@@ -767,6 +758,23 @@ router.get("/oidc/callback", async (req, res) => {
scopes: config.scopes,
});
try {
await authManager.registerOIDCUser(id);
} catch (encryptionError) {
await db.delete(users).where(eq(users.id, id));
authLogger.error(
"Failed to setup OIDC user encryption, user creation rolled back",
encryptionError,
{
operation: "oidc_user_create_encryption_failed",
userId: id,
},
);
return res.status(500).json({
error: "Failed to setup user security - user creation cancelled",
});
}
user = await db.select().from(users).where(eq(users.id, id));
} else {
await db
@@ -779,6 +787,15 @@ router.get("/oidc/callback", async (req, res) => {
const userRecord = user[0];
try {
await authManager.authenticateOIDCUser(userRecord.id);
} catch (setupError) {
authLogger.error("Failed to setup OIDC user encryption", setupError, {
operation: "oidc_user_encryption_setup_failed",
userId: userRecord.id,
});
}
const token = await authManager.generateJWTToken(userRecord.id, {
expiresIn: "50d",
});

View File

@@ -53,6 +53,20 @@ class AuthManager {
await this.userCrypto.setupUserEncryption(userId, password);
}
async registerOIDCUser(userId: string): Promise<void> {
await this.userCrypto.setupOIDCUserEncryption(userId);
}
async authenticateOIDCUser(userId: string): Promise<boolean> {
const authenticated = await this.userCrypto.authenticateOIDCUser(userId);
if (authenticated) {
await this.performLazyEncryptionMigration(userId);
}
return authenticated;
}
async authenticateUser(userId: string, password: string): Promise<boolean> {
const authenticated = await this.userCrypto.authenticateUser(
userId,

View File

@@ -69,6 +69,19 @@ class UserCrypto {
DEK.fill(0);
}
async setupOIDCUserEncryption(userId: string): Promise<void> {
const DEK = crypto.randomBytes(UserCrypto.DEK_LENGTH);
const now = Date.now();
this.userSessions.set(userId, {
dataKey: Buffer.from(DEK),
lastActivity: now,
expiresAt: now + UserCrypto.SESSION_DURATION,
});
DEK.fill(0);
}
async authenticateUser(userId: string, password: string): Promise<boolean> {
try {
const kekSalt = await this.getKEKSalt(userId);
@@ -119,6 +132,52 @@ class UserCrypto {
}
}
async authenticateOIDCUser(userId: string): Promise<boolean> {
try {
const kekSalt = await this.getKEKSalt(userId);
if (!kekSalt) {
await this.setupOIDCUserEncryption(userId);
return true;
}
const systemKey = this.deriveOIDCSystemKey(userId);
const encryptedDEK = await this.getEncryptedDEK(userId);
if (!encryptedDEK) {
systemKey.fill(0);
await this.setupOIDCUserEncryption(userId);
return true;
}
const DEK = this.decryptDEK(encryptedDEK, systemKey);
systemKey.fill(0);
if (!DEK || DEK.length === 0) {
await this.setupOIDCUserEncryption(userId);
return true;
}
const now = Date.now();
const oldSession = this.userSessions.get(userId);
if (oldSession) {
oldSession.dataKey.fill(0);
}
this.userSessions.set(userId, {
dataKey: Buffer.from(DEK),
lastActivity: now,
expiresAt: now + UserCrypto.SESSION_DURATION,
});
DEK.fill(0);
return true;
} catch (error) {
await this.setupOIDCUserEncryption(userId);
return true;
}
}
getUserDataKey(userId: string): Buffer | null {
const session = this.userSessions.get(userId);
if (!session) {
@@ -259,6 +318,18 @@ class UserCrypto {
);
}
private deriveOIDCSystemKey(userId: string): Buffer {
const systemSecret = process.env.OIDC_SYSTEM_SECRET || "termix-oidc-system-secret-default";
const salt = Buffer.from(userId, "utf8");
return crypto.pbkdf2Sync(
systemSecret,
salt,
100000,
UserCrypto.KEK_LENGTH,
"sha256",
);
}
private encryptDEK(dek: Buffer, kek: Buffer): EncryptedDEK {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv("aes-256-gcm", kek, iv);

View File

@@ -428,12 +428,10 @@ export function HomepageAuth({
return;
}
if (success && token) {
if (success) {
setOidcLoading(true);
setError(null);
// JWT token is now automatically set as HttpOnly cookie by backend
console.log("OIDC login successful - JWT set as secure HttpOnly cookie");
getUserInfo()
.then((meRes) => {
setInternalLoggedIn(true);
@@ -455,13 +453,11 @@ export function HomepageAuth({
);
})
.catch((err) => {
toast.error(t("errors.failedUserInfo"));
setInternalLoggedIn(false);
setLoggedIn(false);
setIsAdmin(false);
setUsername(null);
setUserId(null);
// HttpOnly cookies cannot be cleared from JavaScript - backend handles this
window.history.replaceState(
{},
document.title,