mirror of
https://github.com/Steffo99/synapse-account-provisioner.git
synced 2024-12-22 23:04:24 +00:00
it works so it's ok
This commit is contained in:
parent
1aa5c36500
commit
8cba3724c3
2 changed files with 226 additions and 10 deletions
31
index.html
31
index.html
|
@ -8,9 +8,7 @@
|
||||||
<link rel="stylesheet" href="https://unpkg.com/@steffo/bluelib@9.0.1/dist/layouts-center.root.min.css" type="text/css" />
|
<link rel="stylesheet" href="https://unpkg.com/@steffo/bluelib@9.0.1/dist/layouts-center.root.min.css" type="text/css" />
|
||||||
<link rel="stylesheet" href="https://unpkg.com/@steffo/bluelib@9.0.1/dist/colors-royalblue.root.min.css" type="text/css" />
|
<link rel="stylesheet" href="https://unpkg.com/@steffo/bluelib@9.0.1/dist/colors-royalblue.root.min.css" type="text/css" />
|
||||||
<link rel="stylesheet" href="https://unpkg.com/@steffo/bluelib@9.0.1/dist/fonts-fira-ghpages.root.min.css" type="text/css" />
|
<link rel="stylesheet" href="https://unpkg.com/@steffo/bluelib@9.0.1/dist/fonts-fira-ghpages.root.min.css" type="text/css" />
|
||||||
<script>
|
<script src="index.js"></script>
|
||||||
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="theme-bluelib layout-center">
|
<body class="theme-bluelib layout-center">
|
||||||
<header>
|
<header>
|
||||||
|
@ -23,18 +21,18 @@
|
||||||
Create a new account
|
Create a new account
|
||||||
</h2>
|
</h2>
|
||||||
<div class="chapter-1">
|
<div class="chapter-1">
|
||||||
<form class="panel box form-flex">
|
<form class="panel box form-flex" id="form-account">
|
||||||
<h3>
|
<h3>
|
||||||
Homeserver details
|
Homeserver details
|
||||||
</h3>
|
</h3>
|
||||||
<label>
|
<label>
|
||||||
<span>URL</span>
|
<span>URL</span>
|
||||||
<input disabled class="fade" type="text" name="homeserver" placeholder="https://uniberry.info">
|
<input type="text" id="input-homeserver" placeholder="https://uniberry.info">
|
||||||
<span></span>
|
<span></span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span>Secret</span>
|
<span>Secret</span>
|
||||||
<input disabled class="fade" type="text" name="homeserver" placeholder="June-Stardom-Matter-Thinly-Stream3-Curing">
|
<input type="text" id="input-registrationsecret" placeholder="June-Stardom-Matter-Thinly-Stream3-Curing">
|
||||||
<span></span>
|
<span></span>
|
||||||
</label>
|
</label>
|
||||||
<h3>
|
<h3>
|
||||||
|
@ -42,19 +40,24 @@
|
||||||
</h3>
|
</h3>
|
||||||
<label>
|
<label>
|
||||||
<span>Username</span>
|
<span>Username</span>
|
||||||
<input disabled class="fade" type="text" name="username" placeholder="steffo">
|
<input type="text" id="input-username" placeholder="steffo">
|
||||||
|
<span></span>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Display name</span>
|
||||||
|
<input type="text" id="input-displayname" placeholder="Steffo">
|
||||||
<span></span>
|
<span></span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span>Password</span>
|
<span>Password</span>
|
||||||
<input disabled class="fade" type="password" name="password" placeholder="hunter2">
|
<input type="password" id="input-password" placeholder="hunter2">
|
||||||
<span></span>
|
<span></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="form-flex-choice">
|
<div class="form-flex-choice">
|
||||||
<span>Privileges</span>
|
<span>Privileges</span>
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
<input disabled class="fade" type="checkbox" name="isadmin">
|
<input type="checkbox" id="input-isadmin">
|
||||||
Homeserver administrator
|
Homeserver administrator
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,9 +68,17 @@
|
||||||
</h3>
|
</h3>
|
||||||
<label>
|
<label>
|
||||||
<span></span>
|
<span></span>
|
||||||
<button disabled class="fade">Create</button>
|
<button id="button-create">Create</button>
|
||||||
<span></span>
|
<span></span>
|
||||||
</label>
|
</label>
|
||||||
|
<h3>
|
||||||
|
Result
|
||||||
|
</h3>
|
||||||
|
<div class="form-flex-choice">
|
||||||
|
<span></span>
|
||||||
|
<pre><output><code id="output" class="fade">No output yet.</code></output></pre>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
205
index.js
Normal file
205
index.js
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/40031688/javascript-arraybuffer-to-hex
|
||||||
|
const byteToHex = [];
|
||||||
|
for (let n = 0; n <= 0xff; ++n)
|
||||||
|
{
|
||||||
|
const hexOctet = n.toString(16).padStart(2, "0");
|
||||||
|
byteToHex.push(hexOctet);
|
||||||
|
}
|
||||||
|
function bufferToHex(buffer) {
|
||||||
|
const buff = new Uint8Array(buffer);
|
||||||
|
const hexOctets = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < buff.length; ++i)
|
||||||
|
hexOctets.push(byteToHex[buff[i]]);
|
||||||
|
|
||||||
|
return hexOctets.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RequestError extends Error {
|
||||||
|
constructor(message, response) {
|
||||||
|
super(message)
|
||||||
|
this.response = response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SynapseAPI {
|
||||||
|
/**
|
||||||
|
* @param baseURL {URL}
|
||||||
|
* @param registrationSecret {string}
|
||||||
|
*/
|
||||||
|
constructor({ baseURL, registrationSecret }) {
|
||||||
|
this.baseURL = baseURL
|
||||||
|
this.registrationSecret = registrationSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
async getRegistrationNonce() {
|
||||||
|
if(!this.baseURL) throw new Error("Homeserver URL not set.")
|
||||||
|
|
||||||
|
const nonceURL = new URL("/_synapse/admin/v1/register", this.baseURL)
|
||||||
|
const nonceResponse = await fetch(nonceURL)
|
||||||
|
|
||||||
|
if(nonceResponse.status !== 200) {
|
||||||
|
throw new RequestError("Could not get registration nonce.", nonceResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
const nonceData = await nonceResponse.json()
|
||||||
|
return nonceData["nonce"]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param username {string}
|
||||||
|
* @param displayname {string}
|
||||||
|
* @param password {string}
|
||||||
|
* @param admin {boolean}
|
||||||
|
* @returns {Promise<object>}
|
||||||
|
*/
|
||||||
|
async registerAccount(username, displayname, password, admin) {
|
||||||
|
if(!(this.baseURL)) throw new Error("Homeserver URL not set.")
|
||||||
|
if(!(this.registrationSecret)) throw new Error("Registration secret not set.")
|
||||||
|
if(!(window.isSecureContext)) throw new Error("Cannot run outside of secure contexts.")
|
||||||
|
if(!("TextEncoder" in window)) throw new Error("TextEncoder is not supported in this context.")
|
||||||
|
if(!("TextDecoder" in window)) throw new Error("TextDecoder is not supported in this context.")
|
||||||
|
if(!("crypto" in window && crypto.subtle !== undefined)) throw new Error("SubtleCrypto is not supported in this context.")
|
||||||
|
|
||||||
|
const nonce = await this.getRegistrationNonce()
|
||||||
|
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
const registrationSecretBuffer = encoder.encode(this.registrationSecret)
|
||||||
|
|
||||||
|
// noinspection JSUnresolvedReference
|
||||||
|
const key = await crypto.subtle.importKey(
|
||||||
|
"raw",
|
||||||
|
registrationSecretBuffer,
|
||||||
|
{
|
||||||
|
name: "HMAC",
|
||||||
|
hash: "SHA-1",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
["sign"]
|
||||||
|
)
|
||||||
|
|
||||||
|
const adminString = admin ? "admin" : "notadmin"
|
||||||
|
const string = `${nonce}\0${username}\0${password}\0${adminString}`
|
||||||
|
const stringBuffer = encoder.encode(string)
|
||||||
|
|
||||||
|
// noinspection JSUnresolvedReference
|
||||||
|
const macBuffer = await crypto.subtle.sign(
|
||||||
|
"HMAC",
|
||||||
|
key,
|
||||||
|
stringBuffer,
|
||||||
|
)
|
||||||
|
const mac = bufferToHex(macBuffer)
|
||||||
|
|
||||||
|
const registrationURL = new URL("/_synapse/admin/v1/register", this.baseURL)
|
||||||
|
const registrationResponse = await fetch(registrationURL, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
nonce,
|
||||||
|
username,
|
||||||
|
displayname,
|
||||||
|
password,
|
||||||
|
admin,
|
||||||
|
mac,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if(registrationResponse.status !== 200) {
|
||||||
|
throw new RequestError("Failed to register user.", registrationResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await registrationResponse.json()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function onClickRegisterUser(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const homeserverInput = document.getElementById("input-homeserver")
|
||||||
|
const secretInput = document.getElementById("input-registrationsecret")
|
||||||
|
const usernameInput = document.getElementById("input-username")
|
||||||
|
const displaynameInput = document.getElementById("input-displayname")
|
||||||
|
const passwordInput = document.getElementById("input-password")
|
||||||
|
const isadminInput = document.getElementById("input-isadmin")
|
||||||
|
const output = document.getElementById("output")
|
||||||
|
|
||||||
|
output.classList.remove("red")
|
||||||
|
output.classList.remove("green")
|
||||||
|
|
||||||
|
homeserverInput.disabled = true
|
||||||
|
secretInput.disabled = true
|
||||||
|
usernameInput.disabled = true
|
||||||
|
displaynameInput.disabled = true
|
||||||
|
passwordInput.disabled = true
|
||||||
|
isadminInput.disabled = true
|
||||||
|
|
||||||
|
homeserverInput.classList.add("fade")
|
||||||
|
secretInput.classList.add("fade")
|
||||||
|
usernameInput.classList.add("fade")
|
||||||
|
displaynameInput.classList.add("fade")
|
||||||
|
passwordInput.classList.add("fade")
|
||||||
|
isadminInput.classList.add("fade")
|
||||||
|
|
||||||
|
try {
|
||||||
|
const homeserver = homeserverInput.value
|
||||||
|
const secret = secretInput.value
|
||||||
|
const username = usernameInput.value
|
||||||
|
const displayname = displaynameInput.value
|
||||||
|
const password = passwordInput.value
|
||||||
|
const isadmin = isadminInput.checked
|
||||||
|
|
||||||
|
const sapi = new SynapseAPI({
|
||||||
|
baseURL: new URL(homeserver),
|
||||||
|
registrationSecret: secret,
|
||||||
|
})
|
||||||
|
|
||||||
|
let result = await sapi.registerAccount(username, displayname, password, isadmin)
|
||||||
|
|
||||||
|
output.classList.add("green")
|
||||||
|
output.innerText = JSON.stringify(result, null, " ")
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
output.classList.add("red")
|
||||||
|
console.error(e)
|
||||||
|
if("response" in e) {
|
||||||
|
try {
|
||||||
|
const result = await e.response.json()
|
||||||
|
output.innerText = JSON.stringify(result, null, " ")
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
output.innerText = e.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
output.innerText = e.toString()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
homeserverInput.disabled = false
|
||||||
|
secretInput.disabled = false
|
||||||
|
usernameInput.disabled = false
|
||||||
|
displaynameInput.disabled = false
|
||||||
|
passwordInput.disabled = false
|
||||||
|
isadminInput.disabled = false
|
||||||
|
|
||||||
|
homeserverInput.classList.remove("fade")
|
||||||
|
secretInput.classList.remove("fade")
|
||||||
|
usernameInput.classList.remove("fade")
|
||||||
|
displaynameInput.classList.remove("fade")
|
||||||
|
passwordInput.classList.remove("fade")
|
||||||
|
isadminInput.classList.remove("fade")
|
||||||
|
output.classList.remove("fade")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function onload() {
|
||||||
|
const createButton = document.getElementById("button-create")
|
||||||
|
createButton.addEventListener("click", onClickRegisterUser)
|
||||||
|
}
|
Loading…
Reference in a new issue