Compare commits
No commits in common. "main" and "fullstack" have entirely different histories.
10 changed files with 152 additions and 435 deletions
12
blogin.css
12
blogin.css
|
@ -1,6 +1,4 @@
|
||||||
#error {
|
.error {
|
||||||
display: none;
|
|
||||||
|
|
||||||
border: 2px solid red;
|
border: 2px solid red;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
|
@ -9,9 +7,7 @@
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
#success {
|
.info {
|
||||||
display: none;
|
|
||||||
|
|
||||||
border: 2px solid darkgreen;
|
border: 2px solid darkgreen;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
|
@ -19,7 +15,3 @@
|
||||||
background-color: limegreen;
|
background-color: limegreen;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
#submit {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
207
blogin.sh
207
blogin.sh
|
@ -23,104 +23,38 @@ TITLE="blogin"
|
||||||
DESCRIPTION="the worst member of the logiverse"
|
DESCRIPTION="the worst member of the logiverse"
|
||||||
AUTHOR="$USER"
|
AUTHOR="$USER"
|
||||||
TAGS=(meta interactive tadiweb)
|
TAGS=(meta interactive tadiweb)
|
||||||
WRITTEN_AT=2024-12-28T00:00:00Z
|
|
||||||
|
|
||||||
action=$(jq -r '.action // ""' <<< "$BLAG_QUERY" || :)
|
action=$(jq -r '.action // ""' <<< "$BLAG_QUERY" || :)
|
||||||
if [[ "${action:+x}" == "x" ]]; then
|
if [[ "${action:+x}" == "x" ]]; then
|
||||||
RAW=application/json
|
DONT_CACHE=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
metadata
|
if [[ "$action" == "verify_token" ]]; then
|
||||||
|
RAW=application/json
|
||||||
|
metadata
|
||||||
|
|
||||||
case "$action" in
|
|
||||||
verify_token)
|
|
||||||
#shellcheck disable=SC2266
|
#shellcheck disable=SC2266
|
||||||
{
|
{
|
||||||
verified=$(jq -r '.token // ""' <<< "$BLAG_QUERY" | bwt_verify)
|
jq -r '.token // ""' <<< "$BLAG_QUERY" |
|
||||||
[[ "$(jq -r '.till > now' <<< "$verified")" == "true" ]]
|
bwt_verify |
|
||||||
printf '%s' "$verified"
|
[[ "$(jq -r ".till > now")" == "true" ]]
|
||||||
} || echo false
|
} || echo false
|
||||||
exit
|
|
||||||
;;
|
|
||||||
|
|
||||||
posts)
|
exit
|
||||||
db_rows "SET TIME ZONE 'UTC'; SELECT * FROM posts" |
|
fi
|
||||||
|
|
||||||
|
if [[ "$action" == "posts" ]]; then
|
||||||
|
RAW=application/json
|
||||||
|
metadata
|
||||||
|
|
||||||
|
db_rows "SELECT * FROM posts" |
|
||||||
jq -r 'map([.contents, .username, .ts, null, 0]) | select(. != [])'
|
jq -r 'map([.contents, .username, .ts, null, 0]) | select(. != [])'
|
||||||
exit
|
|
||||||
;;
|
|
||||||
|
|
||||||
login)
|
|
||||||
username=$(jq -r '.username' <<< "$BLAG_QUERY" || :)
|
|
||||||
password=$(jq -r '.password' <<< "$BLAG_QUERY" || :)
|
|
||||||
|
|
||||||
if ! printf '%s' "$username" | rg -Uq '^[a-zA-Z0-9_]{3,24}$'; then
|
|
||||||
json_err "username must be between 3 to 24 characters, and must consist of alphanumerical characters plus underscore"
|
|
||||||
exit
|
|
||||||
elif ! printf '%s' "$password" | rg -Uq '^.{8,108}$'; then
|
|
||||||
json_err "password must be between 8 and 108 characters"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
{
|
|
||||||
query="SELECT password FROM users WHERE username=$(escape_sql_str "$username")"
|
|
||||||
row=$(db_row "$query")
|
|
||||||
if [[ "$row" == "null" ]]; then
|
|
||||||
hash=$(printf '%s' "$password" | argon2_hash)
|
|
||||||
echo "registering user $username" >&2
|
|
||||||
unset password
|
|
||||||
db_query "INSERT INTO users (username, password) VALUES ($(escape_sql_str "$username"), $(escape_sql_str "$hash")) ON CONFLICT (username) DO UPDATE SET password=excluded.password" >&2
|
|
||||||
|
|
||||||
created=true
|
|
||||||
else
|
|
||||||
hash=$(jq -r '.password' <<< "$row")
|
|
||||||
if ! argon2_verify "$hash" <<< "$password"; then
|
|
||||||
json_err "invalid password"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
created=false
|
|
||||||
fi
|
|
||||||
|
|
||||||
json_username=$(jq -Rr @json <<< "$username")
|
|
||||||
till=$(date -d "+ $SESSION_TIME" '+%s')
|
|
||||||
|
|
||||||
printf '{"till":%s,"user":%s}' \
|
|
||||||
"$till" \
|
|
||||||
"$json_username" |
|
|
||||||
bwt_sign |
|
|
||||||
jq -R "{ error: false, created: $created, user: $json_username, till: $till, token: . }"
|
|
||||||
} || json_err
|
|
||||||
|
|
||||||
exit
|
exit
|
||||||
;;
|
fi
|
||||||
|
|
||||||
submit)
|
DONT_CACHE=1 # workaround
|
||||||
{
|
metadata
|
||||||
token=$(jq -r '.token // ""' <<< "$BLAG_QUERY" || :)
|
|
||||||
if ! verified=$(check_token "$token"); then
|
|
||||||
json_err "$verified token"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
user=$(jq -r .user <<< "$verified")
|
|
||||||
status=$(jq -r .status <<< "$BLAG_QUERY")
|
|
||||||
chars=${#status}
|
|
||||||
|
|
||||||
if (( chars == 0 || chars > 200 )); then
|
|
||||||
json_err "status should be between 1 and 200 characters"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
db_query "SET TIME ZONE 'UTC'; INSERT INTO posts (username, contents, ts) VALUES (
|
|
||||||
$(escape_sql_str "$user"),
|
|
||||||
$(escape_sql_str "$status"),
|
|
||||||
$(escape_sql_str "$(date -u --rfc-3339=seconds)")::timestamp
|
|
||||||
) ON CONFLICT (username) DO UPDATE
|
|
||||||
SET contents=excluded.contents, ts=excluded.ts" >&2
|
|
||||||
echo '{"error":false}'
|
|
||||||
} || json_err
|
|
||||||
exit
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "<style>"
|
echo "<style>"
|
||||||
cat blogin.css
|
cat blogin.css
|
||||||
|
@ -131,27 +65,96 @@ paragraph <<< "$DESCRIPTION"
|
||||||
|
|
||||||
link "https://git.slonk.ing/slonk/blogin" <<< "view source" | paragraph
|
link "https://git.slonk.ing/slonk/blogin" <<< "view source" | paragraph
|
||||||
|
|
||||||
cat partials/submit.html
|
token=$(jq -r '.token // ""' <<< "$BLAG_QUERY" || :)
|
||||||
cat partials/login.html
|
|
||||||
|
|
||||||
echo '<div id="error"></div>'
|
logged_in=0
|
||||||
echo '<div id="success"></div>'
|
if [[ "$action" == "login" ]]; then
|
||||||
|
username=$(jq -r '.username' <<< "$BLAG_QUERY" || :)
|
||||||
|
password=$(jq -r '.password' <<< "$BLAG_QUERY" || :)
|
||||||
|
|
||||||
|
if ! rg -Uq '^[a-zA-Z0-9_]{3,24}$' <<< "$username"; then
|
||||||
|
error_box "username must be between 3 to 24 characters, and must consist of alphanumerical characters plus underscore"
|
||||||
|
elif ! rg -Uq '^.{8,108}$' <<< "$password"; then
|
||||||
|
error_box "password must be between 8 and 108 characters"
|
||||||
|
else
|
||||||
|
{
|
||||||
|
query="SELECT password FROM users WHERE username=$(escape_sql_str "$username")"
|
||||||
|
row=$(db_row "$query")
|
||||||
|
if [[ "$row" == "null" ]]; then
|
||||||
|
hash=$(printf '%s' "$password" | argon2_hash)
|
||||||
|
echo "registering user $username" >&2
|
||||||
|
unset password
|
||||||
|
db_query "INSERT INTO users (username, password) VALUES ($(escape_sql_str "$username"), $(escape_sql_str "$hash"))" >&2
|
||||||
|
token=$(printf '{"till":%s,"user":%s}' "$(date -d "+ $SESSION_TIME" '+%s')" "$(jq -Rr @json <<< "$username")" | bwt_sign)
|
||||||
|
logged_in=1
|
||||||
|
info_box "account created!"
|
||||||
|
else
|
||||||
|
hash=$(jq -r '.password' <<< "$row")
|
||||||
|
if argon2_verify "$hash" <<< "$password" ; then
|
||||||
|
token=$(printf '{"till":%s,"user":%s}' "$(date -d "+ $SESSION_TIME" '+%s')" "$(jq -Rr @json <<< "$username")" | bwt_sign)
|
||||||
|
logged_in=1
|
||||||
|
info_box "logged in!"
|
||||||
|
else
|
||||||
|
error_box "invalid password"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
} || {
|
||||||
|
error_box "failed to log in"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
elif [[ "${token:+x}" == "x" ]]; then
|
||||||
|
if { verified=$(bwt_verify <<< "$token"); } then
|
||||||
|
till=$(jq -r '.till' <<< "$verified")
|
||||||
|
now=$(date '+%s')
|
||||||
|
if (( till > now )); then
|
||||||
|
user=$(jq -r '.user' <<< "$verified")
|
||||||
|
logged_in=1
|
||||||
|
else
|
||||||
|
# token expired
|
||||||
|
error_box "session expired, please log in again"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "warning: somebody tried something funny" >&2
|
||||||
|
error_box "bwt failed to validate"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( logged_in == 1 )); then
|
||||||
|
escape <<< "hello, $user" | header 2
|
||||||
|
|
||||||
|
if [[ "$action" == "submit" ]]; then
|
||||||
|
{
|
||||||
|
status=$(jq -r .status <<< "$BLAG_QUERY")
|
||||||
|
chars=${#status}
|
||||||
|
|
||||||
|
if (( chars == 0 || chars > 200 )); then
|
||||||
|
error_box "status should be between 1 and 200 characters"
|
||||||
|
fi
|
||||||
|
|
||||||
|
db_query "INSERT INTO posts (username, contents, ts) VALUES (
|
||||||
|
$(escape_sql_str "$user"),
|
||||||
|
$(escape_sql_str "$status"),
|
||||||
|
$(escape_sql_str "$(date --rfc-3339=seconds)")::timestamp
|
||||||
|
) ON CONFLICT (username) DO UPDATE
|
||||||
|
SET contents=excluded.contents, ts=excluded.ts" >&2
|
||||||
|
info_box "updated status :D"
|
||||||
|
} || error_box "failed to update status :("
|
||||||
|
fi
|
||||||
|
|
||||||
|
render partials/submit_form.sh "$token"
|
||||||
|
else
|
||||||
|
header 2 <<< "(b)login"
|
||||||
|
render partials/login_form.sh
|
||||||
|
fi
|
||||||
|
|
||||||
header 2 <<< "posts"
|
header 2 <<< "posts"
|
||||||
|
|
||||||
echo '<div id="posts">loading...</div>'
|
|
||||||
|
|
||||||
#shellcheck disable=SC2162
|
#shellcheck disable=SC2162
|
||||||
#while read POST; do {
|
while read POST; do {
|
||||||
# if [[ "${POST:+x}" == "x" ]]; then
|
if [[ "${POST:+x}" == "x" ]]; then
|
||||||
# declare -A post="$POST"
|
declare -A post="$POST"
|
||||||
# render partials/post.sh "${post[username]}" "${post[instance]}" "${post[timestamp]}" "${post[contents]}"
|
render partials/post.sh "${post[username]}" "${post[instance]}" "${post[timestamp]}" "${post[contents]}"
|
||||||
# fi
|
fi
|
||||||
#} & done < <(parse_posts "${INSTANCES[@]}" || :)
|
} & done < <(parse_posts "${INSTANCES[@]}" || :)
|
||||||
#
|
|
||||||
#wait
|
|
||||||
|
|
||||||
echo '<script>'
|
wait
|
||||||
cat load.js
|
|
||||||
echo '</script>'
|
|
||||||
echo '<noscript>blogin needs javascript to run :(</noscript>'
|
|
||||||
|
|
5
load.js
5
load.js
|
@ -1,5 +0,0 @@
|
||||||
let post_name = location.pathname.match(/^\/posts\/([^\/]+)\/*$/)[1];
|
|
||||||
let el = document.createElement("script");
|
|
||||||
el.src = `/media/${post_name}/blogin.js`;
|
|
||||||
el.type = "module";
|
|
||||||
document.body.appendChild(el);
|
|
|
@ -1,24 +0,0 @@
|
||||||
<div id="login">
|
|
||||||
<h2>(b)login</h2>
|
|
||||||
<form id="login-form">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
minlength="3"
|
|
||||||
maxlength="108"
|
|
||||||
placeholder="username"
|
|
||||||
name="username"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
minlength="8"
|
|
||||||
maxlength="108"
|
|
||||||
placeholder="password"
|
|
||||||
name="password"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<input type="submit" value="log in" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
24
partials/login_form.sh
Normal file
24
partials/login_form.sh
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
cat <<EOF
|
||||||
|
<form action="/posts/blogin">
|
||||||
|
<input type="hidden" name="action" value="login" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
minlength="3"
|
||||||
|
maxlength="108"
|
||||||
|
placeholder="username"
|
||||||
|
name="username"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
minlength="3"
|
||||||
|
maxlength="108"
|
||||||
|
placeholder="password"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<input type="submit" value="log in" />
|
||||||
|
</form>
|
||||||
|
EOF
|
|
@ -1,9 +0,0 @@
|
||||||
<div id="submit">
|
|
||||||
<h2 id="welcome">welcome back!</h2>
|
|
||||||
<form id="submit-form">
|
|
||||||
<input type="text" maxlength="200" name="status" required />
|
|
||||||
<br />
|
|
||||||
<input type="submit" value="update blatus" />
|
|
||||||
<input type="button" value="log out" onclick="logout()" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
10
partials/submit_form.sh
Normal file
10
partials/submit_form.sh
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
cat <<EOF
|
||||||
|
<form action="/posts/blogin" method="GET">
|
||||||
|
<input type="hidden" name="action" value="submit" />
|
||||||
|
<input type="hidden" name="token" value="$(escape <<< "$1")" />
|
||||||
|
<input type="text" maxlength="200" name="status" required />
|
||||||
|
<br />
|
||||||
|
<input type="submit" value="update blatus" />
|
||||||
|
</form>
|
||||||
|
EOF
|
||||||
|
|
240
static/blogin.js
240
static/blogin.js
|
@ -1,240 +0,0 @@
|
||||||
import config from "./config.js";
|
|
||||||
|
|
||||||
console.log(config);
|
|
||||||
|
|
||||||
let token = localStorage.getItem("token");
|
|
||||||
let verified = false;
|
|
||||||
let loggedIn = false;
|
|
||||||
|
|
||||||
const loginDiv = document.getElementById("login");
|
|
||||||
const loginForm = document.getElementById("login-form");
|
|
||||||
|
|
||||||
const submitDiv = document.getElementById("submit");
|
|
||||||
const submitForm = document.getElementById("submit-form");
|
|
||||||
const welcomeHeader = document.getElementById("welcome");
|
|
||||||
|
|
||||||
const posts = document.getElementById("posts");
|
|
||||||
|
|
||||||
const errorBox = document.getElementById("error");
|
|
||||||
const successBox = document.getElementById("success");
|
|
||||||
|
|
||||||
let errorHideTimeout = null;
|
|
||||||
let successHideTimeout = null;
|
|
||||||
|
|
||||||
function displayError(message) {
|
|
||||||
let p = document.createElement("p");
|
|
||||||
p.innerText = message;
|
|
||||||
|
|
||||||
errorBox.appendChild(p);
|
|
||||||
errorBox.style.display = "block";
|
|
||||||
|
|
||||||
setTimeout(() => p.remove(), config.box_timeout);
|
|
||||||
|
|
||||||
if (errorHideTimeout !== null) clearTimeout(errorHideTimeout);
|
|
||||||
errorHideTimeout = setTimeout(() => {
|
|
||||||
errorBox.style.display = "none";
|
|
||||||
errorHideTimeout = null;
|
|
||||||
}, config.box_timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
function displaySuccess(message) {
|
|
||||||
let p = document.createElement("p");
|
|
||||||
p.innerText = message;
|
|
||||||
|
|
||||||
successBox.appendChild(p);
|
|
||||||
successBox.style.display = "block";
|
|
||||||
|
|
||||||
setTimeout(() => p.remove(), config.box_timeout);
|
|
||||||
|
|
||||||
if (successHideTimeout !== null) clearTimeout(successHideTimeout);
|
|
||||||
successHideTimeout = setTimeout(() => {
|
|
||||||
successBox.style.display = "none";
|
|
||||||
successHideTimeout = null;
|
|
||||||
}, config.box_timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function verifyToken(token) {
|
|
||||||
let url = new URL(config.endpoint);
|
|
||||||
url.searchParams.set("action", "verify_token");
|
|
||||||
url.searchParams.set("token", token);
|
|
||||||
try {
|
|
||||||
return await (await fetch(url)).json();
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
displayError(err.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.login = login;
|
|
||||||
|
|
||||||
let logoutTimeout = null;
|
|
||||||
|
|
||||||
function login(verified) {
|
|
||||||
loggedIn = true;
|
|
||||||
welcomeHeader.innerText = `welcome, ${verified.user}`;
|
|
||||||
if (logoutTimeout !== null) clearTimeout(logoutTimeout);
|
|
||||||
logoutTimeout = setTimeout(() => {
|
|
||||||
logout();
|
|
||||||
logoutTimeout = null;
|
|
||||||
}, verified.till * 1000 - Date.now());
|
|
||||||
|
|
||||||
loginDiv.style.display = "none";
|
|
||||||
submitDiv.style.display = "block";
|
|
||||||
}
|
|
||||||
|
|
||||||
function logout() {
|
|
||||||
loggedIn = false;
|
|
||||||
localStorage.removeItem("token");
|
|
||||||
token = null;
|
|
||||||
|
|
||||||
loginDiv.style.display = "block";
|
|
||||||
submitDiv.style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token && (verified = await verifyToken(token))) login(verified);
|
|
||||||
else logout();
|
|
||||||
|
|
||||||
loginForm.onsubmit = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
let data = new FormData(loginForm);
|
|
||||||
|
|
||||||
let url = new URL(config.endpoint);
|
|
||||||
url.searchParams.set("action", "login");
|
|
||||||
for (let [key, value] of data.entries()) {
|
|
||||||
url.searchParams.set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let value = await (await fetch(url)).json();
|
|
||||||
console.log(value);
|
|
||||||
|
|
||||||
if (value.error) {
|
|
||||||
displayError(`couldn't log in: ${value.reason ?? "unknown error"}`);
|
|
||||||
} else {
|
|
||||||
token = value.token;
|
|
||||||
localStorage.setItem("token", value.token);
|
|
||||||
verified = {
|
|
||||||
user: value.user,
|
|
||||||
till: value.till,
|
|
||||||
};
|
|
||||||
login(verified);
|
|
||||||
if (value.created) displaySuccess("account created!");
|
|
||||||
else displaySuccess(`logged in!`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
displayError(err.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
submitForm.onsubmit = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (!loggedIn) return;
|
|
||||||
|
|
||||||
let data = new FormData(submitForm);
|
|
||||||
|
|
||||||
let url = new URL(config.endpoint);
|
|
||||||
url.searchParams.set("action", "submit");
|
|
||||||
url.searchParams.set("token", token);
|
|
||||||
for (let [key, value] of data.entries()) {
|
|
||||||
url.searchParams.set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let value = await (await fetch(url)).json();
|
|
||||||
console.log(value);
|
|
||||||
|
|
||||||
if (value.error) {
|
|
||||||
displayError(
|
|
||||||
`couldn't update status: ${value.reason ?? "unknown error"}`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
updateStatuses();
|
|
||||||
displaySuccess(`updated status!`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
displayError(err.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function renderStatus(status) {
|
|
||||||
let quote = document.createElement("blockquote");
|
|
||||||
quote.classList.add("post");
|
|
||||||
|
|
||||||
let meta = document.createElement("div");
|
|
||||||
meta.classList.add("meta");
|
|
||||||
|
|
||||||
let username = document.createElement("span");
|
|
||||||
username.classList.add("username");
|
|
||||||
username.innerText = status.username;
|
|
||||||
let at = document.createElement("span");
|
|
||||||
at.classList.add("at");
|
|
||||||
at.innerText = "@";
|
|
||||||
let instance = document.createElement("a");
|
|
||||||
instance.classList.add("instance");
|
|
||||||
instance.innerText = status.instance;
|
|
||||||
instance.href = "https://" + status.instance;
|
|
||||||
instance.target = "_blank";
|
|
||||||
let timestamp = document.createElement("span");
|
|
||||||
timestamp.classList.add("timestamp", "date", "date-rfc3339");
|
|
||||||
let d =
|
|
||||||
Date.parse(status.timestamp) + new Date().getTimezoneOffset() * 60000;
|
|
||||||
timestamp.innerText = new Date(d).toISOString();
|
|
||||||
meta.append(username, at, instance, " at ", timestamp);
|
|
||||||
quote.appendChild(meta);
|
|
||||||
|
|
||||||
let contents = document.createElement("p");
|
|
||||||
contents.innerText = status.contents;
|
|
||||||
quote.appendChild(contents);
|
|
||||||
|
|
||||||
return quote;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateStatuses() {
|
|
||||||
let statuses = [];
|
|
||||||
let promises = [];
|
|
||||||
for (let [instance, endpoint] of Object.entries(config.instances)) {
|
|
||||||
promises.push(
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
console.log(instance, endpoint);
|
|
||||||
let res = await fetch(endpoint);
|
|
||||||
console.log(res);
|
|
||||||
let value = await res.json();
|
|
||||||
|
|
||||||
for (let [contents, username, timestamp] of value)
|
|
||||||
statuses.push({
|
|
||||||
username,
|
|
||||||
timestamp,
|
|
||||||
contents,
|
|
||||||
instance,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
displayError(err.message);
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
statuses.sort(({ timestamp: a }, { timestamp: b }) => b - a);
|
|
||||||
|
|
||||||
let start = performance.now();
|
|
||||||
|
|
||||||
statuses = statuses.map(renderStatus);
|
|
||||||
|
|
||||||
let diff = performance.now() - start;
|
|
||||||
console.log(diff);
|
|
||||||
|
|
||||||
posts.replaceChildren(...statuses);
|
|
||||||
replaceDates();
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialization code
|
|
||||||
window.logout = logout;
|
|
||||||
updateStatuses();
|
|
|
@ -1,15 +0,0 @@
|
||||||
// who up logi they verse
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
endpoint: `${location.protocol}//${location.host}${location.pathname}`,
|
|
||||||
instances: {
|
|
||||||
"todepond.com": "https://todepond-lablogingetusers.web.val.run",
|
|
||||||
"svenlaa.com": "https://api.svenlaa.com/logiverse/logs",
|
|
||||||
"evolved.systems": "https://evol-lablogingetusers.web.val.run",
|
|
||||||
},
|
|
||||||
box_timeout: 5000,
|
|
||||||
};
|
|
||||||
|
|
||||||
config.instances[location.hostname] = `${config.endpoint}?action=posts`;
|
|
||||||
|
|
||||||
export default config;
|
|
33
util.sh
33
util.sh
|
@ -6,11 +6,11 @@ bwt_sign() {
|
||||||
"$(jq -Rsr @json <<< "$data")" \
|
"$(jq -Rsr @json <<< "$data")" \
|
||||||
"$(jq -Rsr @json <<< "$signature")" |
|
"$(jq -Rsr @json <<< "$signature")" |
|
||||||
zstd -1 | base64 -w0 |
|
zstd -1 | base64 -w0 |
|
||||||
sed 's,=,_E,g;s,/,_S,g;s,+,_P,g' # urlsafe base64 but evil
|
sed 's,=,_E_,g;s,/,_S_,g;s,+,_P_,g' # urlsafe base64 but evil
|
||||||
}
|
}
|
||||||
|
|
||||||
bwt_verify() {
|
bwt_verify() {
|
||||||
json=$(sed 's,_E,=,g;s,_S,/,g;s,_P,+,g' | base64 -d | zstd -d)
|
json=$(sed 's,_E_,=,g;s,_S_,/,g;s,_P_,+,g' | base64 -d | zstd -d)
|
||||||
data=$(jq -r '.d' <<< "$json")
|
data=$(jq -r '.d' <<< "$json")
|
||||||
signature=$(jq -r '.s' <<< "$json")
|
signature=$(jq -r '.s' <<< "$json")
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ bwt_verify() {
|
||||||
}
|
}
|
||||||
|
|
||||||
db_query() {
|
db_query() {
|
||||||
psql "${PSQL_OPTS[@]}" --csv -qc "${1?need query}"
|
psql "${PSQL_OPTS[@]}" --csv -c "${1?need query}"
|
||||||
}
|
}
|
||||||
|
|
||||||
db_rows() {
|
db_rows() {
|
||||||
|
@ -44,7 +44,7 @@ argon2_hash() {
|
||||||
}
|
}
|
||||||
|
|
||||||
argon2_verify() {
|
argon2_verify() {
|
||||||
$(cash -O2 -pipe -largon2 <<< '
|
$(cash -O2 -pipe -largon2 <<EOF
|
||||||
#include <argon2.h>
|
#include <argon2.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <error.h>
|
#include <error.h>
|
||||||
|
@ -57,7 +57,9 @@ int main(int argc, char **argv) {
|
||||||
|
|
||||||
if (result == ARGON2_OK) return 0;
|
if (result == ARGON2_OK) return 0;
|
||||||
else return 1;
|
else return 1;
|
||||||
}') "${1?need hash}" "$(</dev/stdin)"
|
}
|
||||||
|
EOF
|
||||||
|
) "${1?need hash}" "$(</dev/stdin)"
|
||||||
}
|
}
|
||||||
|
|
||||||
error_box() {
|
error_box() {
|
||||||
|
@ -92,24 +94,3 @@ parse_posts() {
|
||||||
|
|
||||||
rm "$pipe"
|
rm "$pipe"
|
||||||
}
|
}
|
||||||
|
|
||||||
json_err() {
|
|
||||||
printf '%s' "$1" | jq -R '{ error: true, reason: (if . == "" then null else . end) }'
|
|
||||||
}
|
|
||||||
|
|
||||||
check_token() {
|
|
||||||
if ! verified=$(bwt_verify <<< "$1"); then
|
|
||||||
printf 'invalid'
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
till=$(jq -r '.till' <<< "$verified")
|
|
||||||
now=$(date '+%s')
|
|
||||||
|
|
||||||
if (( till < now )); then
|
|
||||||
printf 'expired'
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf '%s' "$verified"
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue