feat: run on a single port (#3163)

* start v2

* start

* run

* some cleanup

* remove v2 pkg again

* simplify

* webauthn

* remove unused config

* fix login path in Dockerfile

* fix asset_generator.go

* health handler

* fix grpc web

* refactor

* merge

* build new main.go

* run new main.go

* update logging pkg

* fix error msg

* update logging

* cleanup

* cleanup

* go mod tidy

* change localDevMode

* fix customEndpoints

* update logging

* comments

* change local flag to external configs

* fix location generated go code

* fix

Co-authored-by: fforootd <florian@caos.ch>
This commit is contained in:
Livio Amstutz
2022-02-14 17:22:30 +01:00
committed by GitHub
parent 2f3a482ade
commit 389eb4a27a
306 changed files with 1708 additions and 1567 deletions

View File

@@ -0,0 +1,62 @@
const avatars = document.getElementsByClassName('lgn-avatar');
for (let i = 0; i < avatars.length; i++) {
const displayName = avatars[i].getAttribute('loginname');
if (displayName) {
const username = displayName.split('@')[0];
let separator = '_';
if (username.includes('-')) {
separator = '-';
}
if (username.includes('.')) {
separator = '.';
}
const split = username.split(separator);
const initials = split[0].charAt(0) + (split[1] ? split[1].charAt(0) : '');
avatars[i].getElementsByClassName('initials')[0].innerHTML = initials;
avatars[i].style.background = this.getColor(displayName);
// set default white text instead of contrast text mode
avatars[i].style.color = '#ffffff';
}
}
function getColor(userName) {
const colors = [
'linear-gradient(40deg, #B44D51 30%, rgb(241,138,138))',
'linear-gradient(40deg, #B75073 30%, rgb(234,96,143))',
'linear-gradient(40deg, #84498E 30%, rgb(214,116,230))',
'linear-gradient(40deg, #705998 30%, rgb(163,131,220))',
'linear-gradient(40deg, #5C6598 30%, rgb(135,148,222))',
'linear-gradient(40deg, #7F90D3 30%, rgb(181,196,247))',
'linear-gradient(40deg, #3E93B9 30%, rgb(150,215,245))',
'linear-gradient(40deg, #3494A0 30%, rgb(71,205,222))',
'linear-gradient(40deg, #25716A 30%, rgb(58,185,173))',
'linear-gradient(40deg, #427E41 30%, rgb(97,185,96))',
'linear-gradient(40deg, #89A568 30%, rgb(176,212,133))',
'linear-gradient(40deg, #90924D 30%, rgb(187,189,98))',
'linear-gradient(40deg, #E2B032 30%, rgb(245,203,99))',
'linear-gradient(40deg, #C97358 30%, rgb(245,148,118))',
'linear-gradient(40deg, #6D5B54 30%, rgb(152,121,108))',
'linear-gradient(40deg, #6B7980 30%, rgb(134,163,177))',
];
let hash = 0;
if (userName.length === 0) {
return colors[hash];
}
hash = this.hashCode(userName);
return colors[hash % colors.length];
}
function hashCode(str, seed = 0) {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}

View File

@@ -0,0 +1,68 @@
/*
* modified version of:
*
* base64-arraybuffer
* https://github.com/niklasvh/base64-arraybuffer
*
* Copyright (c) 2012 Niklas von Hertzen
* Licensed under the MIT license.
*/
"use strict";
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// Use a lookup table to find the index.
let lookup = new Uint8Array(256);
for (var i = 0; i < chars.length; i++) {
lookup[chars.charCodeAt(i)] = i;
}
function encode(arraybuffer) {
let bytes = new Uint8Array(arraybuffer),
i, len = bytes.length, base64 = "";
for (i = 0; i < len; i += 3) {
base64 += chars[bytes[i] >> 2];
base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
base64 += chars[bytes[i + 2] & 63];
}
if ((len % 3) === 2) {
base64 = base64.substring(0, base64.length - 1) + "=";
} else if (len % 3 === 1) {
base64 = base64.substring(0, base64.length - 2) + "==";
}
return base64;
}
function decode(base64) {
let bufferLength = base64.length * 0.75,
len = base64.length, i, p = 0,
encoded1, encoded2, encoded3, encoded4;
if (base64[base64.length - 1] === "=") {
bufferLength--;
if (base64[base64.length - 2] === "=") {
bufferLength--;
}
}
let arraybuffer = new ArrayBuffer(bufferLength),
bytes = new Uint8Array(arraybuffer);
for (i = 0; i < len; i += 4) {
encoded1 = lookup[base64.charCodeAt(i)];
encoded2 = lookup[base64.charCodeAt(i + 1)];
encoded3 = lookup[base64.charCodeAt(i + 2)];
encoded4 = lookup[base64.charCodeAt(i + 3)];
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
}
return arraybuffer;
}

View File

@@ -0,0 +1,17 @@
function CheckChangePwPolicy() {
let policyElement = document.getElementById("change-new-password");
let pwNew = policyElement.value;
let pwNewConfirmation = document.getElementById("change-password-confirmation").value;
if (ComplexityPolicyCheck(policyElement, pwNew, pwNewConfirmation) === false) {
policyElement.setAttribute("color", "warn");
return false;
} else {
policyElement.setAttribute("color", "primary");
}
return pwNew == pwNewConfirmation;
}
let button = document.getElementById("change-password-button");
disableSubmit(CheckChangePwPolicy, button);

View File

@@ -0,0 +1,6 @@
const copyToClipboard = str => {
navigator.clipboard.writeText(str);
};
let copyButton = document.getElementById("copy");
copyButton.addEventListener("click", copyToClipboard(copyButton.getAttribute("data-copy")));

View File

@@ -0,0 +1,3 @@
let button = document.getElementById("submit-button");
disableSubmit(undefined, button);

View File

@@ -0,0 +1,10 @@
let buttons1 = document.getElementsByName("linkbutton");
for (let i = 0; i < buttons1.length; i++) {
disableSubmit(undefined, buttons1[i]);
}
let buttons2 = document.getElementsByName("autoregisterbutton");
for (let i = 0; i < buttons2.length; i++) {
disableSubmit(undefined, buttons2[i]);
}

View File

@@ -0,0 +1,58 @@
function disableSubmit(checks, button) {
let form = document.getElementsByTagName('form')[0];
let inputs = form.getElementsByTagName('input');
if (button) {
button.disabled = true;
}
addRequiredEventListener(inputs, checks, form, button);
disableDoubleSubmit(form, button);
toggleButton(checks, form, inputs, button);
}
function addRequiredEventListener(inputs, checks, form, button) {
let eventType = 'input';
for (i = 0; i < inputs.length; i++) {
if (inputs[i].required) {
eventType = 'input';
if (inputs[i].type === 'checkbox') {
eventType = 'click';
}
inputs[i].addEventListener(eventType, function () {
toggleButton(checks, form, inputs, button);
});
}
}
}
function disableDoubleSubmit(form, button) {
form.addEventListener('submit', function () {
document.body.classList.add('waiting');
button.disabled = true;
});
}
function toggleButton(checks, form, inputs, button) {
if (checks !== undefined) {
if (checks() === false) {
button.disabled = true;
return;
}
}
const targetValue = !allRequiredDone(form, inputs);
button.disabled = targetValue;
}
function allRequiredDone(form, inputs) {
for (i = 0; i < inputs.length; i++) {
if (inputs[i].required) {
if (inputs[i].type === 'checkbox' && !inputs[i].checked) {
return false;
}
if (inputs[i].value === '') {
return false;
}
}
}
return true;
}

View File

@@ -0,0 +1,14 @@
function CheckInitPwPolicy() {
let policyElement = document.getElementById("password");
let pwNew = policyElement.value;
let pwNewConfirmation = document.getElementById("passwordconfirm").value;
if (ComplexityPolicyCheck(policyElement, pwNew, pwNewConfirmation) === false) {
return false;
}
return pwNew == pwNewConfirmation;
}
let button = document.getElementById("init-button");
disableSubmit(CheckInitPwPolicy, button);

View File

@@ -0,0 +1,14 @@
const suffix = document.getElementById('default-login-suffix');
const suffixInput = document.getElementsByClassName('lgn-suffix-input')[0];
if (suffix && suffixInput) {
calculateOffset();
suffix.addEventListener("DOMCharacterDataModified", calculateOffset);
}
function calculateOffset() {
// add suffix width to inner right padding of the input field
if (suffix && suffixInput) {
suffixInput.style.paddingRight = `${(suffix.offsetWidth ?? 0) + 10}px`;
}
}

View File

@@ -0,0 +1,17 @@
document.addEventListener('DOMContentLoaded', function () {
autoSubmit();
});
function autoSubmit() {
let form = document.getElementsByTagName('form')[0];
if (form) {
let button = document.getElementById("redirect-button");
if (button) {
button.addEventListener("click", function (event) {
location.reload();
event.preventDefault();
});
}
form.submit();
}
}

View File

@@ -0,0 +1,38 @@
function RenderDefaultLoginnameSuffix() {
let orgNameText = document.getElementById("orgname").value;
let userName = document.getElementById("username");
let defaultLoginNameSuffix = document.getElementById("default-login-suffix");
let iamDomain = userName.dataset.iamDomain;
let orgDomain = orgNameText.replace(" ", "-");
if (orgDomain !== "") {
defaultLoginNameSuffix.innerText = "@" + orgDomain.toLowerCase() + "." + iamDomain;
} else {
defaultLoginNameSuffix.innerText = "";
}
offsetLabel();
}
window.addEventListener('DOMContentLoaded', (event) => {
RenderDefaultLoginnameSuffix();
});
document.getElementById("orgname").addEventListener('input', function () {
RenderDefaultLoginnameSuffix();
});
function offsetLabel() {
const suffix = document.getElementById('default-login-suffix');
const suffixInput = document.getElementsByClassName('lgn-suffix-input')[0];
calculateOffset();
suffix.addEventListener("DOMCharacterDataModified", calculateOffset);
function calculateOffset() {
// add suffix width to inner right padding of the input field
if (suffix && suffixInput) {
suffixInput.style.paddingRight = `${(suffix.offsetWidth ?? 0) + 10}px`;
}
}
}

View File

@@ -0,0 +1,83 @@
function ComplexityPolicyCheck(policyElement, pwNew, pwNewConfirmation) {
let minLength = policyElement.dataset.minlength;
let upperRegex = policyElement.dataset.hasUppercase;
let lowerRegex = policyElement.dataset.hasLowercase;
let numberRegex = policyElement.dataset.hasNumber;
let symbolRegex = policyElement.dataset.hasSymbol;
let valid = true;
let minlengthelem = document.getElementById('minlength');
if (pwNew.length >= minLength) {
ValidPolicy(minlengthelem);
valid = true;
} else {
InvalidPolicy(minlengthelem);
valid = false;
}
let upper = document.getElementById('uppercase');
if (upperRegex !== "") {
if (RegExp(upperRegex).test(pwNew)) {
ValidPolicy(upper);
valid = true;
} else {
InvalidPolicy(upper);
valid = false;
}
}
let lower = document.getElementById('lowercase');
if (lowerRegex !== "") {
if (RegExp(lowerRegex).test(pwNew)) {
ValidPolicy(lower);
valid = true;
} else {
InvalidPolicy(lower);
valid = false;
}
}
let number = document.getElementById('number');
if (numberRegex != "") {
if (RegExp(numberRegex).test(pwNew)) {
ValidPolicy(number);
valid = true;
} else {
InvalidPolicy(number);
valid = false;
}
}
let symbol = document.getElementById('symbol');
if (symbolRegex != "") {
if (RegExp(symbolRegex).test(pwNew)) {
ValidPolicy(symbol);
valid = true;
} else {
InvalidPolicy(symbol);
valid = false;
}
}
let confirmation = document.getElementById('confirmation');
if (pwNew === pwNewConfirmation && pwNewConfirmation !== "" ) {
ValidPolicy(confirmation);
valid = true;
} else {
InvalidPolicy(confirmation);
valid = false;
}
return valid;
}
function ValidPolicy(element) {
element.classList.remove('invalid');
element.getElementsByTagName('i')[0].classList.remove('lgn-icon-times-solid');
element.getElementsByTagName('i')[0].classList.remove('lgn-warn');
element.getElementsByTagName('i')[0].classList.add('lgn-icon-check-solid');
element.getElementsByTagName('i')[0].classList.add('lgn-valid');
}
function InvalidPolicy(element) {
element.classList.add('invalid');
element.getElementsByTagName('i')[0].classList.remove('lgn-valid');
element.getElementsByTagName('i')[0].classList.remove('lgn-icon-check-solid');
element.getElementsByTagName('i')[0].classList.add('lgn-warn');
element.getElementsByTagName('i')[0].classList.add('lgn-icon-times-solid');
}

View File

@@ -0,0 +1,17 @@
function CheckRegisterPwPolicy() {
let policyElement = document.getElementById("register-password");
let pwNew = policyElement.value;
let pwNewConfirmation = document.getElementById("register-password-confirmation").value;
if (ComplexityPolicyCheck(policyElement, pwNew, pwNewConfirmation) === false) {
policyElement.setAttribute("color", "warn");
return false;
} else {
policyElement.setAttribute("color", "primary");
}
return pwNew == pwNewConfirmation;
}
let button = document.getElementById("register-button");
disableSubmit(CheckRegisterPwPolicy, button);

View File

@@ -0,0 +1,46 @@
const usesDarkTheme = hasDarkModeOverwriteCookie() || (!hasLightModeOverwriteCookie() && window.matchMedia('(prefers-color-scheme: dark)').matches);
if (usesDarkTheme) {
document.documentElement.classList.replace('lgn-light-theme', 'lgn-dark-theme');
writeModeCookie('dark');
} else {
document.documentElement.classList.replace('lgn-dark-theme', 'lgn-light-theme');
writeModeCookie('light');
}
function hasDarkModeOverwriteCookie() {
return getCookie('mode') === 'dark';
}
function hasLightModeOverwriteCookie() {
return getCookie('mode') === 'light';
}
function writeModeCookie(mode) {
let cookieMode = getCookie('mode')
if (cookieMode === '' || cookieMode.startsWith('auto')) {
setCookie('mode', 'auto-' + mode, 365);
}
}
function getCookie(cname) {
let name = cname + '=';
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1);
}
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length);
}
}
return '';
}
function setCookie(name, value, exdays) {
let d = new Date();
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
let expires = "expires=" + d.toUTCString();
document.cookie = name + "=" + value + ";" + expires + ";path=/";
}

View File

@@ -0,0 +1,31 @@
function checkWebauthnSupported(button, func) {
let support = document.getElementsByClassName("wa-support");
let noSupport = document.getElementsByClassName("wa-no-support");
if (!window.PublicKeyCredential) {
for (let item of noSupport) {
item.classList.remove('hidden');
}
for (let item of support) {
item.classList.add('hidden');
}
return;
}
document.getElementById(button).addEventListener('click', func);
}
function webauthnError(error) {
let err = document.getElementById('wa-error');
err.getElementsByClassName('cause')[0].innerText = error.message;
err.classList.remove('hidden');
}
function bufferDecode(value) {
return decode(value);
}
function bufferEncode(value) {
return encode(value)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "");
}

View File

@@ -0,0 +1,41 @@
document.addEventListener('DOMContentLoaded', checkWebauthnSupported('btn-login', login));
function login() {
document.getElementById('wa-error').classList.add('hidden');
let makeAssertionOptions = JSON.parse(atob(document.getElementsByName('credentialAssertionData')[0].value));
makeAssertionOptions.publicKey.challenge = bufferDecode(makeAssertionOptions.publicKey.challenge);
makeAssertionOptions.publicKey.allowCredentials.forEach(function (listItem) {
listItem.id = bufferDecode(listItem.id)
});
navigator.credentials.get({
publicKey: makeAssertionOptions.publicKey
}).then(function (credential) {
verifyAssertion(credential);
}).catch(function (err) {
webauthnError(err);
});
}
function verifyAssertion(assertedCredential) {
let authData = new Uint8Array(assertedCredential.response.authenticatorData);
let clientDataJSON = new Uint8Array(assertedCredential.response.clientDataJSON);
let rawId = new Uint8Array(assertedCredential.rawId);
let sig = new Uint8Array(assertedCredential.response.signature);
let userHandle = new Uint8Array(assertedCredential.response.userHandle);
let data = JSON.stringify({
id: assertedCredential.id,
rawId: bufferEncode(rawId),
type: assertedCredential.type,
response: {
authenticatorData: bufferEncode(authData),
clientDataJSON: bufferEncode(clientDataJSON),
signature: bufferEncode(sig),
userHandle: bufferEncode(userHandle),
},
})
document.getElementsByName('credentialData')[0].value = btoa(data);
document.getElementsByTagName('form')[0].submit();
}

View File

@@ -0,0 +1,42 @@
document.addEventListener('DOMContentLoaded', checkWebauthnSupported('btn-register', registerCredential));
function registerCredential() {
document.getElementById('wa-error').classList.add('hidden');
let opt = JSON.parse(atob(document.getElementsByName('credentialCreationData')[0].value));
opt.publicKey.challenge = bufferDecode(opt.publicKey.challenge);
opt.publicKey.user.id = bufferDecode(opt.publicKey.user.id);
if (opt.publicKey.excludeCredentials) {
for (let i = 0; i < opt.publicKey.excludeCredentials.length; i++) {
if (opt.publicKey.excludeCredentials[i].id !== null) {
opt.publicKey.excludeCredentials[i].id = bufferDecode(opt.publicKey.excludeCredentials[i].id);
}
}
}
navigator.credentials.create({
publicKey: opt.publicKey
}).then(function (credential) {
createCredential(credential);
}).catch(function (err) {
webauthnError(err);
});
}
function createCredential(newCredential) {
let attestationObject = new Uint8Array(newCredential.response.attestationObject);
let clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
let rawId = new Uint8Array(newCredential.rawId);
let data = JSON.stringify({
id: newCredential.id,
rawId: bufferEncode(rawId),
type: newCredential.type,
response: {
attestationObject: bufferEncode(attestationObject),
clientDataJSON: bufferEncode(clientDataJSON),
},
});
document.getElementsByName('credentialData')[0].value = btoa(data);
document.getElementsByTagName('form')[0].submit();
}