frontend support for password
This commit is contained in:
@@ -3,6 +3,7 @@ twig:
|
||||
strict_variables: true
|
||||
globals:
|
||||
env:
|
||||
allow_password: '%env(STATIC_SECRET_ENABLED)%'
|
||||
title: '%env(TITLE)%'
|
||||
bg_color: '%env(BG_COLOR)%'
|
||||
fg_color: '%env(FG_COLOR)%'
|
||||
@@ -10,8 +11,10 @@ twig:
|
||||
return_field: '%env(QUERY_PREFIX)%return'
|
||||
id_field: '%env(QUERY_PREFIX)%id'
|
||||
token_field: '%env(QUERY_PREFIX)%token'
|
||||
password_field: '%env(QUERY_PREFIX)%password'
|
||||
id_name: '%env(ID_NAME)%'
|
||||
token_name: '%env(TOKEN_NAME)%'
|
||||
password_name: '%env(PASSWORD_NAME)%'
|
||||
submit_name: '%env(SUBMIT_NAME)%'
|
||||
error_message: '%env(ERROR_MESSAGE)%'
|
||||
teapot_title: '%env(TEAPOT_TITLE)%'
|
||||
|
||||
@@ -26,12 +26,12 @@ parameters:
|
||||
# --- extra variables ---
|
||||
# query parameter prefix to prevent collisions
|
||||
env(QUERY_PREFIX): '_preauth_'
|
||||
# allow files in /app/public/assets directory to be served, false to disable
|
||||
env(ASSETS): '1' # default enabled, boolean
|
||||
# how long do we allow all traffic from an ip address after successful login
|
||||
# could be useful if you have a system which does not handle cookies
|
||||
env(IP_TTL): '0' # default disabled, time in seconds
|
||||
# if desired, in addition to supporting a TOTP, you can set a static password
|
||||
# TODO rely on checking enabled, instead of the secret directly throughout the code
|
||||
env(STATIC_SECRET_ENABLED): '0' # boolean
|
||||
env(STATIC_SECRET): '' # default disabled
|
||||
# once blocked, do we respond with "I'm a teapot", false to use "Too many requests"
|
||||
env(TEAPOT): '1' # boolean
|
||||
@@ -43,6 +43,7 @@ parameters:
|
||||
env(ERROR_COLOR): '#ffb16d'
|
||||
env(ID_NAME): 'Session ID'
|
||||
env(TOKEN_NAME): 'Authentication Token'
|
||||
env(PASSWORD_NAME): 'Authentication Password'
|
||||
env(SUBMIT_NAME): 'Submit'
|
||||
env(ERROR_MESSAGE): 'Unsuccessful login attempt'
|
||||
# title and message to use on block page, if teapot is true
|
||||
@@ -59,8 +60,8 @@ parameters:
|
||||
app.query_prefix: '%env(QUERY_PREFIX)%'
|
||||
app.totp_uri: '%env(TOTP_URI)%'
|
||||
|
||||
app.assets: '%env(ASSETS)%'
|
||||
app.ip_ttl: '%env(IP_TTL)%'
|
||||
app.static_secret_enabled: '%env(STATIC_SECRET_ENABLED)%'
|
||||
app.static_secret: '%env(STATIC_SECRET)%'
|
||||
app.teapot: '%env(TEAPOT)%'
|
||||
|
||||
|
||||
+2
-1
@@ -27,7 +27,8 @@
|
||||
#IP_TTL=0 # default disabled, time in seconds
|
||||
|
||||
# if desired, in addition to supporting a TOTP, you can set a static password
|
||||
#STATIC_SECRET='' # deafult disabled
|
||||
#STATIC_SECRET_ENABLED='0' # boolean, disabled by default
|
||||
#STATIC_SECRET='' # default disabled
|
||||
|
||||
# once blocked, do we respond with "I'm a teapot", false to use "Too many requests"
|
||||
#TEAPOT=true # default enabled, boolean
|
||||
|
||||
+2
-9
@@ -15,7 +15,6 @@ final readonly class ConfigBag {
|
||||
private int $limitTtl;
|
||||
private string $queryPrefix;
|
||||
private string $totpUri;
|
||||
private ?string $assetsDir;
|
||||
private ?int $ipTtl;
|
||||
private ?string $staticSecret;
|
||||
private bool $teapot;
|
||||
@@ -33,9 +32,8 @@ final readonly class ConfigBag {
|
||||
#[Autowire('%app.limit_ttl%')] int $limitTtl,
|
||||
#[Autowire('%app.query_prefix%')] string $queryPrefix,
|
||||
#[Autowire('%app.totp_uri%')] string $totpUri,
|
||||
#[Autowire('%app.assets%')] bool $assets,
|
||||
#[Autowire('%kernel.project_dir%/public/assets/')] string $assetsDir,
|
||||
#[Autowire('%app.ip_ttl%')] ?int $ipTtl,
|
||||
#[Autowire('%app.static_secret_enabled%')] bool $staticSecretEnabled,
|
||||
#[Autowire('%app.static_secret%')] ?string $staticSecret,
|
||||
#[Autowire('%app.teapot%')] bool $teapot,
|
||||
#[Autowire('%app.error_message%')] string $errorMessage,
|
||||
@@ -49,9 +47,8 @@ final readonly class ConfigBag {
|
||||
$this->limitTtl = ($limitTtl >= 1) ? $limitTtl : 86400;
|
||||
$this->queryPrefix = $queryPrefix;
|
||||
$this->totpUri = $totpUri ?: $utilities->loadTotp();
|
||||
$this->assetsDir = $assets ? $assetsDir : null;
|
||||
$this->ipTtl = $ipTtl ?: null;
|
||||
$this->staticSecret = $staticSecret ?: null;
|
||||
$this->staticSecret = $staticSecretEnabled ? ($staticSecret ?: null) : null;
|
||||
$this->teapot = $teapot;
|
||||
$this->errorMessage = $errorMessage;
|
||||
$this->teapotTitle = $teapotTitle;
|
||||
@@ -86,10 +83,6 @@ final readonly class ConfigBag {
|
||||
return $this->totpUri;
|
||||
}
|
||||
|
||||
public function assetsDir(): ?string {
|
||||
return $this->assetsDir;
|
||||
}
|
||||
|
||||
public function ipTtl(): ?int {
|
||||
return $this->ipTtl;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,13 @@ html { background-color: {{ env.bg_color }}; color: {{ env.fg_color }}; display:
|
||||
body { display: table-cell; vertical-align: middle; }
|
||||
h1 { font-size: 2.5em; font-weight: normal; text-align: center; }
|
||||
p { color: {{ env.error_color }}; text-align: center; }
|
||||
form { display: flex; flex-wrap: wrap; justify-content: center; }
|
||||
form { align-items: baseline; display: flex; flex-wrap: wrap; justify-content: center; }
|
||||
form div { width: 45%; }
|
||||
div.right { text-align: right; }
|
||||
div.center { text-align: center; }
|
||||
div.hidden { display: none; }
|
||||
span { cursor: pointer; font-size: 0.75em; text-decoration: underline; }
|
||||
button { background-color: #cccccc; }
|
||||
input { background-color: #ffffff; max-width: 100%; }
|
||||
button, input { border: 0.0625em solid #333333; border-radius: 0.25em; color: #333333; font-size: 0.9em; }
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{ env.title }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
{{ include('_style.html.twig') }}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
+67
-10
@@ -8,25 +8,82 @@
|
||||
<div class="right"><label for="preauth-id">{{ env.id_name }}:</label></div>
|
||||
<div><input type="text" name="preauth_id" id="preauth-id"
|
||||
autocomplete="username" required="required" autofocus="autofocus"></div>
|
||||
<div class="right"><label for="preauth-token">{{ env.token_name }}:</label></div>
|
||||
<div><input type="text" name="preauth_token" id="preauth-token"
|
||||
autocomplete="one-time-code" required="required"></div>
|
||||
{% if env.allow_password %}
|
||||
<div class="right boxTk">
|
||||
<label for="preauth-token">{{ env.token_name }}:</label><br>
|
||||
<span id="preauth-use-pw">🔃 {{ env.password_name }}</span></div>
|
||||
<div class="boxTk"><input type="text" name="preauth_token" id="preauth-token"
|
||||
autocomplete="one-time-code" required="required"></div>
|
||||
|
||||
<div class="right boxPw hidden">
|
||||
<label for="preauth-password">{{ env.password_name }}:</label><br>
|
||||
<span id="preauth-use-tk">🔃 {{ env.token_name }}</span></div>
|
||||
<div class="boxPw hidden"><input type="password" name="preauth_password" id="preauth-password"
|
||||
autocomplete="password" disabled="disabled" required="required"></div>
|
||||
{% else %}
|
||||
<div class="right"><label for="preauth-token">{{ env.token_name }}:</label></div>
|
||||
<div><input type="text" name="preauth_token" id="preauth-token"
|
||||
autocomplete="one-time-code" required="required"></div>
|
||||
{% endif %}
|
||||
<div class="center"><button type="submit">{{ env.submit_name }}</button></div>
|
||||
</form>
|
||||
<script>
|
||||
const form = document.getElementById('preauth-form');
|
||||
const message = document.getElementById('preauth-message');
|
||||
|
||||
{% if env.allow_password %}
|
||||
const usePw = document.getElementById('preauth-use-pw');
|
||||
const useTk = document.getElementById('preauth-use-tk');
|
||||
const boxPw = document.querySelectorAll('.boxPw');
|
||||
const boxTk = document.querySelectorAll('.boxTk');
|
||||
|
||||
usePw.addEventListener('click', (event) => {
|
||||
form.preauth_token.value = '';
|
||||
form.preauth_token.disabled = true;
|
||||
form.preauth_password.disabled = false;
|
||||
boxTk.forEach((element) => {
|
||||
element.classList.add('hidden');
|
||||
});
|
||||
boxPw.forEach((element) => {
|
||||
element.classList.remove('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
useTk.addEventListener('click', (event) => {
|
||||
form.preauth_password.value = '';
|
||||
form.preauth_password.disabled = true;
|
||||
form.preauth_token.disabled = false;
|
||||
boxPw.forEach((element) => {
|
||||
element.classList.add('hidden');
|
||||
});
|
||||
boxTk.forEach((element) => {
|
||||
element.classList.remove('hidden');
|
||||
});
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
form.addEventListener('submit', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
/* make bas64url string containing our payload json object */
|
||||
const data = btoa(JSON.stringify({
|
||||
id: form.preauth_id.value,
|
||||
token: form.preauth_token.value,
|
||||
nonce: form.preauth_nonce.value,
|
||||
json: true
|
||||
})).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
{% if env.allow_password %}
|
||||
/* make base64url string containing our payload json object */
|
||||
/* payload will contain either token or password */
|
||||
const data = btoa(JSON.stringify({
|
||||
id: form.preauth_id.value,
|
||||
...( form.preauth_token.value && { token: form.preauth_token.value }),
|
||||
...(( ! form.preauth_token.value) && { password: form.preauth_password.value }),
|
||||
nonce: form.preauth_nonce.value,
|
||||
json: true
|
||||
})).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
{% else %}
|
||||
/* make base64url string containing our payload json object */
|
||||
const data = btoa(JSON.stringify({
|
||||
id: form.preauth_id.value,
|
||||
token: form.preauth_token.value,
|
||||
nonce: form.preauth_nonce.value,
|
||||
json: true
|
||||
})).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
{% endif %}
|
||||
|
||||
/* send our request to the server */
|
||||
fetch(window.location.href, {
|
||||
|
||||
Reference in New Issue
Block a user