mirror of
https://github.com/Steffo99/synapse-account-provisioner.git
synced 2024-12-22 14:54:25 +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/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" />
|
||||
<script>
|
||||
|
||||
</script>
|
||||
<script src="index.js"></script>
|
||||
</head>
|
||||
<body class="theme-bluelib layout-center">
|
||||
<header>
|
||||
|
@ -23,18 +21,18 @@
|
|||
Create a new account
|
||||
</h2>
|
||||
<div class="chapter-1">
|
||||
<form class="panel box form-flex">
|
||||
<form class="panel box form-flex" id="form-account">
|
||||
<h3>
|
||||
Homeserver details
|
||||
</h3>
|
||||
<label>
|
||||
<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>
|
||||
</label>
|
||||
<label>
|
||||
<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>
|
||||
</label>
|
||||
<h3>
|
||||
|
@ -42,19 +40,24 @@
|
|||
</h3>
|
||||
<label>
|
||||
<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>
|
||||
</label>
|
||||
<label>
|
||||
<span>Password</span>
|
||||
<input disabled class="fade" type="password" name="password" placeholder="hunter2">
|
||||
<input type="password" id="input-password" placeholder="hunter2">
|
||||
<span></span>
|
||||
</label>
|
||||
<div class="form-flex-choice">
|
||||
<span>Privileges</span>
|
||||
<div>
|
||||
<label>
|
||||
<input disabled class="fade" type="checkbox" name="isadmin">
|
||||
<input type="checkbox" id="input-isadmin">
|
||||
Homeserver administrator
|
||||
</label>
|
||||
</div>
|
||||
|
@ -65,9 +68,17 @@
|
|||
</h3>
|
||||
<label>
|
||||
<span></span>
|
||||
<button disabled class="fade">Create</button>
|
||||
<button id="button-create">Create</button>
|
||||
<span></span>
|
||||
</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>
|
||||
</div>
|
||||
</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