import {
ALG,
base64decode,
getHeaders,
getSchema,
setRefresh,
setTokenSigningKey,
tryCompress
} from '/assets/{{ version }}/js/core.js';
async function hash(
domain,
account,
password,
mfa_code,
) {
const txt_encoder = new TextEncoder();
const encoded_domain = txt_encoder.encode(domain);
const encoded_account = txt_encoder.encode(account);
const encoded_password = txt_encoder.encode(password);
const all = new Uint8Array([...encoded_domain, ...encoded_account, ...encoded_password]);
const schema = await getSchema();
const alg = ALG[schema.auth.client_hash]
const password_hash = await crypto.subtle.digest(alg, all);
if (mfa_code) {
const encoded_mfa_code = txt_encoder.encode(mfa_code);
const mfa_all = new Uint8Array([...encoded_domain, ...encoded_account, ...encoded_mfa_code]);
const mfa_hash = await crypto.subtle.digest(alg, mfa_all);
return [encoded_account, new Uint8Array(password_hash), new Uint8Array(mfa_hash)]
} else {
return [encoded_account, new Uint8Array(password_hash)];
}
}
export async function register(
account,
password,
domain,
invite_token = null,
) {
const schema = await getSchema();
if (schema.auth.invite && !invite_token) throw new Error('invite token must be included');
const [encoded_account, password_hash] = await hash(domain, account, password);
if (encoded_account.length > 255) throw new Error('encoded account cannot be longer than 255 bytes');
const keypair = await crypto.subtle.generateKey(
{
name: 'X25519',
},
true,
["deriveKey"],
);
const pubKeyBytes = new Uint8Array(
await crypto.subtle.exportKey("raw", keypair.publicKey),
);
const body = new Uint8Array([
encoded_account.length,
...encoded_account,
...password_hash,
...pubKeyBytes,
]);
const res = await fetch('/accounts/registration/js', {
method: 'POST',
body,
headers: {
...await getHeaders(),
...(invite_token ? {'authorization': invite_token} : {}),
},
});
const res_bytes = await res.bytes();
const ephemeralPubKey = await crypto.subtle.importKey(
'raw',
res_bytes.slice(0, 32),
{
name: 'X25519',
},
false,
[]
);
const nonce = res_bytes.slice(32, 44);
const ciphertext = res_bytes.slice(44);
const sharedSecret = await crypto.subtle.deriveKey(
{
name: 'X25519',
public: ephemeralPubKey,
},
keypair.privateKey,
{
name: 'AES-GCM',
length: 256,
},
false,
["decrypt"],
);
const decrypted = await crypto.subtle.decrypt(
{name: 'AES-GCM', iv: nonce},
sharedSecret,
ciphertext,
);
const decoder = new TextDecoder();
const [recovery_codes_str, mfa_svg] = decoder.decode(decrypted).split('__');
const recovery_codes = recovery_codes_str.match(/.{1,11}/g);
return [mfa_svg, recovery_codes];
}
export async function login(
account,
password,
mfa_code,
domain,
get_cookies,
) {
const [encoded_account, password_hash, mfa_hash] = await hash(domain, account, password, mfa_code);
if (encoded_account.length > 255) throw new Error('encoded account cannot be longer than 255 bytes');
const schema = await getSchema();
let body;
if (schema.auth.cookies_enabled) {
body = new Uint8Array([encoded_account.length, ...encoded_account, ...mfa_hash, ...password_hash]);
} else {
const keypair = await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
const pubKeyBytes = new Uint8Array(
await crypto.subtle.exportKey("raw", keypair.publicKey),
);
const privKeyBytes = new Uint8Array(
await crypto.subtle.exportKey("pkcs8", keypair.privateKey),
);
setTokenSigningKey(privKeyBytes);
body = new Uint8Array([
encoded_account.length,
...encoded_account,
...mfa_hash,
...password_hash,
...pubKeyBytes
]);
}
const res = await fetch('/accounts/login/js', {
method: 'POST',
body,
credentials: get_cookies ? 'include' : 'omit',
headers: await getHeaders({cookies: get_cookies}),
});
const refresh_token = await res.bytes();
setRefresh(refresh_token);
return refresh_token;
}
export async function json(
method,
path,
authorization = false,
object = null,
) {
if (object && method !== 'GET') {
const {body, headers} = await tryCompress(
JSON.stringify(object),
'application/json'
);
return fetch(path, {
method,
body,
headers: {
...headers,
...(await getHeaders({authorization})),
},
credentials: 'omit',
})
} else {
return fetch(path, {
method,
headers: {
...(await getHeaders({authorization})),
},
credentials: 'omit',
})
}
}