mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-16 12:33:03 +00:00
feat: Implement basic auth for applications
This commit is contained in:
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -18,5 +18,14 @@
|
||||
"ts",
|
||||
"json"
|
||||
],
|
||||
"i18n-ally.extract.autoDetect": true
|
||||
"i18n-ally.extract.autoDetect": true,
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/Thumbs.db": true
|
||||
},
|
||||
"hide-files.files": []
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Application" ADD COLUMN "basicAuthPw" TEXT;
|
||||
ALTER TABLE "Application" ADD COLUMN "basicAuthUser" TEXT;
|
||||
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_ApplicationSettings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isDBBranching" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isCustomSSL" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isHttp2" BOOLEAN NOT NULL DEFAULT false,
|
||||
"basicAuth" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isHttp2", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isHttp2", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings";
|
||||
DROP TABLE "ApplicationSettings";
|
||||
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -135,6 +135,8 @@ model Application {
|
||||
dockerRegistryId String?
|
||||
dockerRegistryImageName String?
|
||||
simpleDockerfile String?
|
||||
basicAuthUser String?
|
||||
basicAuthPw String?
|
||||
|
||||
persistentStorage ApplicationPersistentStorage[]
|
||||
secrets Secret[]
|
||||
@@ -187,6 +189,7 @@ model ApplicationSettings {
|
||||
isDBBranching Boolean @default(false)
|
||||
isCustomSSL Boolean @default(false)
|
||||
isHttp2 Boolean @default(false)
|
||||
basicAuth Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
|
||||
@@ -504,12 +504,17 @@ export async function saveApplicationSettings(
|
||||
isBot,
|
||||
isDBBranching,
|
||||
isCustomSSL,
|
||||
isHttp2
|
||||
isHttp2,
|
||||
basicAuth,
|
||||
basicAuthUser,
|
||||
basicAuthPw
|
||||
} = request.body;
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
data: {
|
||||
fqdn: isBot ? null : undefined,
|
||||
basicAuthUser,
|
||||
basicAuthPw,
|
||||
settings: {
|
||||
update: {
|
||||
debug,
|
||||
@@ -519,7 +524,8 @@ export async function saveApplicationSettings(
|
||||
isBot,
|
||||
isDBBranching,
|
||||
isCustomSSL,
|
||||
isHttp2
|
||||
isHttp2,
|
||||
basicAuth,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -43,6 +43,9 @@ export interface SaveApplicationSettings extends OnlyId {
|
||||
isDBBranching: boolean;
|
||||
isCustomSSL: boolean;
|
||||
isHttp2: boolean;
|
||||
basicAuth: boolean;
|
||||
basicAuthUser: string;
|
||||
basicAuthPw: string;
|
||||
};
|
||||
}
|
||||
export interface DeleteApplication extends OnlyId {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FastifyRequest } from 'fastify';
|
||||
import { errorHandler, getDomain, isDev, prisma, executeCommand } from '../../../lib/common';
|
||||
import { errorHandler, executeCommand, getDomain, isDev, prisma } from '../../../lib/common';
|
||||
import { getTemplates } from '../../../lib/services';
|
||||
import { OnlyId } from '../../../types';
|
||||
import { parseAndFindServiceTemplates } from '../../api/v1/services/handlers';
|
||||
@@ -48,20 +48,30 @@ function generateRouters(
|
||||
isWWW,
|
||||
isDualCerts,
|
||||
isCustomSSL,
|
||||
isHttp2 = false
|
||||
isHttp2 = false,
|
||||
basicAuth = false,
|
||||
basicAuthUser = '',
|
||||
basicAuthPw = ''
|
||||
) {
|
||||
let rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
|
||||
let ruleWWW = `Host(\`www.${nakedDomain}\`)${
|
||||
const rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
|
||||
const ruleWWW = `Host(\`www.${nakedDomain}\`)${
|
||||
pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''
|
||||
}`;
|
||||
let http: any = {
|
||||
|
||||
const httpBasicAuth: any = {
|
||||
basicauth: {
|
||||
users: [Buffer.from(basicAuthUser + ':' + basicAuthPw).toString('base64')]
|
||||
}
|
||||
};
|
||||
|
||||
const http: any = {
|
||||
entrypoints: ['web'],
|
||||
rule,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
middlewares: []
|
||||
};
|
||||
let https: any = {
|
||||
const https: any = {
|
||||
entrypoints: ['websecure'],
|
||||
rule,
|
||||
service: `${serviceId}`,
|
||||
@@ -71,14 +81,14 @@ function generateRouters(
|
||||
},
|
||||
middlewares: []
|
||||
};
|
||||
let httpWWW: any = {
|
||||
const httpWWW: any = {
|
||||
entrypoints: ['web'],
|
||||
rule: ruleWWW,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
middlewares: []
|
||||
};
|
||||
let httpsWWW: any = {
|
||||
const httpsWWW: any = {
|
||||
entrypoints: ['websecure'],
|
||||
rule: ruleWWW,
|
||||
service: `${serviceId}`,
|
||||
@@ -97,6 +107,10 @@ function generateRouters(
|
||||
httpsWWW.middlewares.push('redirect-to-non-www');
|
||||
delete https.tls;
|
||||
delete httpsWWW.tls;
|
||||
|
||||
if (basicAuth) {
|
||||
http.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. http + www only
|
||||
@@ -108,6 +122,10 @@ function generateRouters(
|
||||
https.middlewares.push('redirect-to-www');
|
||||
delete https.tls;
|
||||
delete httpsWWW.tls;
|
||||
|
||||
if (basicAuth) {
|
||||
httpWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
}
|
||||
// 5. https + non-www only
|
||||
if (isHttps && !isWWW) {
|
||||
@@ -136,6 +154,10 @@ function generateRouters(
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (basicAuth) {
|
||||
https.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
}
|
||||
// 6. https + www only
|
||||
if (isHttps && isWWW) {
|
||||
@@ -145,6 +167,11 @@ function generateRouters(
|
||||
http.middlewares.push('redirect-to-www');
|
||||
https.middlewares.push('redirect-to-www');
|
||||
}
|
||||
|
||||
if (basicAuth) {
|
||||
httpsWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
|
||||
if (isCustomSSL) {
|
||||
if (isDualCerts) {
|
||||
https.tls = true;
|
||||
@@ -166,23 +193,23 @@ function generateRouters(
|
||||
}
|
||||
}
|
||||
if (isHttp2) {
|
||||
let http2 = {
|
||||
const http2 = {
|
||||
...http,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
let http2WWW = {
|
||||
const http2WWW = {
|
||||
...httpWWW,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
let https2 = {
|
||||
const https2 = {
|
||||
...https,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
|
||||
let https2WWW = {
|
||||
const https2WWW = {
|
||||
...httpsWWW,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
@@ -198,14 +225,21 @@ function generateRouters(
|
||||
[`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW }
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
||||
const result = {
|
||||
[`${serviceId}-${pathPrefix}`]: { ...http },
|
||||
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
|
||||
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
|
||||
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW }
|
||||
};
|
||||
|
||||
if (basicAuth) {
|
||||
result[`${serviceId}-${pathPrefix}-basic-auth`] = { ...httpBasicAuth };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote: boolean = false) {
|
||||
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote = false) {
|
||||
const traefik = {
|
||||
tls: {
|
||||
certificates: []
|
||||
@@ -298,7 +332,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
});
|
||||
}
|
||||
|
||||
let parsedCertificates = [];
|
||||
const parsedCertificates = [];
|
||||
for (const certificate of certificates) {
|
||||
parsedCertificates.push({
|
||||
certFile: `${sslpath}/${certificate.id}-cert.pem`,
|
||||
@@ -369,7 +403,9 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
dockerComposeConfiguration,
|
||||
destinationDocker,
|
||||
destinationDockerId,
|
||||
settings
|
||||
settings,
|
||||
basicAuthUser,
|
||||
basicAuthPw
|
||||
} = application;
|
||||
if (!destinationDockerId) {
|
||||
continue;
|
||||
@@ -424,7 +460,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const { previews, dualCerts, isCustomSSL, isHttp2 } = settings;
|
||||
const { previews, dualCerts, isCustomSSL, isHttp2, basicAuth } = settings;
|
||||
const { network, id: dockerId } = destinationDocker;
|
||||
if (!fqdn) {
|
||||
continue;
|
||||
@@ -446,7 +482,10 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isCustomSSL,
|
||||
isHttp2
|
||||
isHttp2,
|
||||
basicAuth,
|
||||
basicAuthUser,
|
||||
basicAuthPw
|
||||
)
|
||||
};
|
||||
traefik.http.services = {
|
||||
@@ -482,7 +521,11 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isCustomSSL
|
||||
isCustomSSL,
|
||||
false,
|
||||
basicAuth,
|
||||
basicAuthUser,
|
||||
basicAuthPw
|
||||
)
|
||||
};
|
||||
traefik.http.services = {
|
||||
@@ -542,7 +585,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
if (isDomainAndProxyConfiguration.length > 0) {
|
||||
const template: any = await parseAndFindServiceTemplates(service, null, true);
|
||||
const { proxy } = template.services[oneService] || found.services[oneService];
|
||||
for (let configuration of proxy) {
|
||||
for (const configuration of proxy) {
|
||||
if (configuration.hostPort) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -196,6 +196,9 @@
|
||||
"domain_fqdn": "Domain (FQDN)",
|
||||
"https_explainer": "If you specify <span class='text-settings '>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-settings '>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-white '>You must set your DNS to point to the server IP in advance.</span>",
|
||||
"ssl_www_and_non_www": "Generate SSL for www and non-www?",
|
||||
"basic_auth": "Basic Auth",
|
||||
"basic_auth_user": "User",
|
||||
"basic_auth_pw": "Password",
|
||||
"ssl_explainer": "It will generate certificates for both www and non-www. <br>You need to have <span class=' text-settings'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both.",
|
||||
"install_command": "Install Command",
|
||||
"build_command": "Build Command",
|
||||
|
||||
@@ -29,27 +29,27 @@
|
||||
export let application: any;
|
||||
export let settings: any;
|
||||
|
||||
import yaml from 'js-yaml';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import Select from 'svelte-select';
|
||||
import { get, getAPIUrl, post } from '$lib/api';
|
||||
import cuid from 'cuid';
|
||||
import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common';
|
||||
import Beta from '$lib/components/Beta.svelte';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import Setting from '$lib/components/Setting.svelte';
|
||||
import {
|
||||
addToast,
|
||||
appSession,
|
||||
checkIfDeploymentEnabledApplications,
|
||||
setLocation,
|
||||
status,
|
||||
features,
|
||||
isDeploymentEnabled,
|
||||
features
|
||||
setLocation,
|
||||
status
|
||||
} from '$lib/store';
|
||||
import { t } from '$lib/translations';
|
||||
import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common';
|
||||
import Setting from '$lib/components/Setting.svelte';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import Beta from '$lib/components/Beta.svelte';
|
||||
import cuid from 'cuid';
|
||||
import yaml from 'js-yaml';
|
||||
import { onMount } from 'svelte';
|
||||
import Select from 'svelte-select';
|
||||
import { saveForm } from './utils';
|
||||
|
||||
const { id } = $page.params;
|
||||
@@ -77,6 +77,7 @@
|
||||
let isCustomSSL = application.settings?.isCustomSSL;
|
||||
let autodeploy = application.settings?.autodeploy;
|
||||
let isBot = application.settings?.isBot;
|
||||
let basicAuth = application.settings?.basicAuth;
|
||||
let isDBBranching = application.settings?.isDBBranching;
|
||||
let htmlUrl = application.gitSource?.htmlUrl;
|
||||
let isHttp2 = application.settings.isHttp2;
|
||||
@@ -186,6 +187,10 @@
|
||||
if (name === 'isCustomSSL') {
|
||||
isCustomSSL = !isCustomSSL;
|
||||
}
|
||||
if (name === 'basicAuth') {
|
||||
basicAuth = !basicAuth;
|
||||
// TODO: Set user and password
|
||||
}
|
||||
if (name === 'isBot') {
|
||||
if ($status.application.overallStatus !== 'stopped') return;
|
||||
isBot = !isBot;
|
||||
@@ -210,7 +215,10 @@
|
||||
isCustomSSL,
|
||||
isHttp2,
|
||||
branch: application.branch,
|
||||
projectId: application.projectId
|
||||
projectId: application.projectId,
|
||||
basicAuth,
|
||||
basicAuthUser: application.basicAuthUser,
|
||||
basicAuthPw: application.basicAuthPw
|
||||
});
|
||||
return addToast({
|
||||
message: $t('application.settings_saved'),
|
||||
@@ -232,6 +240,9 @@
|
||||
if (name === 'isBot') {
|
||||
isBot = !isBot;
|
||||
}
|
||||
if (name === 'basicAuth') {
|
||||
basicAuth = !basicAuth;
|
||||
}
|
||||
if (name === 'isDBBranching') {
|
||||
isDBBranching = !isDBBranching;
|
||||
}
|
||||
@@ -498,7 +509,7 @@
|
||||
<div class="title font-bold pb-3">General</div>
|
||||
{#if $appSession.isAdmin}
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
class="btn btn-sm btn-primary"
|
||||
type="submit"
|
||||
class:loading={loading.save}
|
||||
class:bg-orange-600={forceSave}
|
||||
@@ -751,7 +762,56 @@
|
||||
on:click={() => !isDisabled && changeSettings('dualCerts')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
id="basicAuth"
|
||||
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
||||
disabled={isDisabled}
|
||||
isCenter={false}
|
||||
bind:setting={basicAuth}
|
||||
title={$t('application.basic_auth')}
|
||||
description="Activate basic authentication for your application. <br>Useful if you want to protect your application with a password. <br><br>Use the <span class='font-bold text-settings'>username</span> and <span class='font-bold text-settings'>password</span> fields to set the credentials."
|
||||
on:click={() => !isDisabled && changeSettings('basicAuth')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if basicAuth}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="basicAuthUser">{$t('application.basic_auth_user')}</label>
|
||||
<input
|
||||
bind:this={fqdnEl}
|
||||
class="w-full"
|
||||
required={!application.settings?.basicAuth}
|
||||
readonly={isDisabled}
|
||||
disabled={isDisabled}
|
||||
name="basicAuthUser"
|
||||
id="basicAuthUser"
|
||||
class:border={!application.settings?.basicAuth && !application.basicAuthUser}
|
||||
class:border-red-500={!application.settings?.basicAuth &&
|
||||
!application.basicAuthUser}
|
||||
bind:value={application.basicAuthUser}
|
||||
placeholder="eg: admin"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="basicAuthPw">{$t('application.basic_auth_pw')}</label>
|
||||
<input
|
||||
bind:this={fqdnEl}
|
||||
class="w-full"
|
||||
required={!application.settings?.basicAuth}
|
||||
readonly={isDisabled}
|
||||
disabled={isDisabled}
|
||||
name="basicAuthPw"
|
||||
id="basicAuthPw"
|
||||
class:border={!application.settings?.basicAuth && !application.basicAuthPw}
|
||||
class:border-red-500={!application.settings?.basicAuth && !application.basicAuthPw}
|
||||
bind:value={application.basicAuthPw}
|
||||
placeholder="**********"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if isHttps && application.buildPack !== 'compose'}
|
||||
<div class="grid grid-cols-2 items-center pb-4">
|
||||
<Setting
|
||||
@@ -782,7 +842,7 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-flow-row gap-2 px-4 pr-5">
|
||||
<div class="grid grid-cols-2 items-center pt-4">
|
||||
<div class="grid grid-cols-2 items-center pt-4">
|
||||
<label for="simpleDockerfile">Dockerfile</label>
|
||||
<div class="flex gap-2">
|
||||
<textarea
|
||||
|
||||
3347
pnpm-lock.yaml
generated
3347
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user