ai.chat/authenticate.js

153 lines
4.5 KiB
JavaScript

export class LoginIdentity {
constructor(baseUrl) {
this._baseUrl = baseUrl;
this._username = null;
this._token = null;
}
get authenticated() { return (!!this._token); }
get username(){ return this._username; }
get token(){ return this._token; }
fetch(url, options={}){
if (!this._token)
throw "Unauthorized";
if (!options.headers)
options.headers = {}
options.headers["Authorization"] = "Token " + this._token;
return window.fetch(url, options)
.then(response => {
if (response.status === 401){
this._token = null;
this._username = null;
this.saveToken();
}
return response;
});
}
saveToken(){
let backup = { token: this._token, username: this._username };
localStorage.setItem("identity", JSON.stringify(backup));
}
restoreToken(){
let restore = JSON.parse(localStorage.getItem("identity") || '{ "token": null, "username": null }');
this._token = restore.token;
this._username = restore.username;
if (this._token){
this.fetch(this._baseUrl + "/v1/checkToken")
.then(response => {
if (!response.ok)
{
this._token = null;
this._username = null;
} else {
this._token = restore.token;
this._username = restore.username;
}
}, (reason)=>{
console.log(reason);
this._token = null;
this._username = null;
});
}
this._token = null;
this._username = null;
}
authenticate(username, password) {
return fetch(this._baseUrl + '/v1/login?username=' + encodeURIComponent(username))
.then((resp)=>resp.json())
.then((r)=>this.hashChallenge(password, r.salt, r.challenge))
.then((r)=>fetch(
this._baseUrl + '/v1/authenticate?username=' + username + '&response=' + encodeURIComponent(this.encodeBase64(r.response))))
.then((r)=>{
if (!r.ok)
throw "Unauthorized";
return r.json();
})
.then((r)=>{
this._username = r.username;
this._token = r.token;
this.saveToken();
})
;
}
decodeBase64(b64){
let bs = atob(b64);
let ui8 = new Uint8Array(bs.length);
for (let i = 0; i < ui8.length; i++)
ui8[i] = bs.charCodeAt(i);
return ui8;
}
encodeBase64(bytes){
const bs = Array.from(bytes, (byte) => String.fromCodePoint(byte), ).join("");
let b64 = btoa(bs);
return b64;
}
async hashPassword(password, salt) {
let bpassword = new TextEncoder().encode(password);
let bsalt = this.decodeBase64(salt);
return this.hash(bsalt, bpassword);
}
async hashChallenge(password, salt, challenge) {
let hashedPassword = await this.hashPassword(password, salt);
let bsalt = this.decodeBase64(salt);
let bchallenge = this.decodeBase64(challenge);
console.log("MMM", hashedPassword, bsalt, bchallenge);
const data = this.concatTypedArrays(bchallenge, hashedPassword, bchallenge);
let signatureBuffer = await this.hash(bsalt, data);
console.log("response", signatureBuffer);
return { challenge: challenge, response: signatureBuffer };
}
async hash(key, data){
const cryptoKey = await window.crypto.subtle.importKey(
"raw",
key,
{name: "HMAC", hash: "SHA-512"},
false,
["sign"]
);
// Konvertiere das Byte-Array in ein ArrayBuffer
const dataBuffer = data.buffer;
// Berechne den HMAC-SHA512-Hash
const signatureBuffer = await window.crypto.subtle.sign(
"HMAC",
cryptoKey,
dataBuffer
);
return new Uint8Array(signatureBuffer);
}
concatTypedArrays(...typedArrays) {
let concatLength = 0;
let offset = 0;
typedArrays.forEach((v)=>{ concatLength += v.length; });
let r = new Uint8Array(concatLength);
typedArrays.forEach((v)=>{
r.set(v, offset);
offset += v.length;
});
return r;
}
}