366 lines
13 KiB
TypeScript
366 lines
13 KiB
TypeScript
interface Window {
|
|
grecaptcha: GReCaptcha;
|
|
}
|
|
|
|
interface GReCaptcha {
|
|
render(container: string | HTMLElement, parameters: {
|
|
sitekey: string;
|
|
theme?: "dark" | "light";
|
|
size?: "compact" | "normal";
|
|
|
|
tabindex?: number;
|
|
|
|
callback?: (token: string) => any;
|
|
"expired-callback"?: () => any;
|
|
"error-callback"?: (error: any) => any;
|
|
}) : string; /* widget_id */
|
|
|
|
reset(widget_id?: string);
|
|
}
|
|
|
|
namespace forum {
|
|
export namespace gcaptcha {
|
|
export async function initialize() {
|
|
if(typeof(window.grecaptcha) === "undefined") {
|
|
let script = document.createElement("script");
|
|
script.async = true;
|
|
|
|
let timeout;
|
|
const callback_name = "captcha_callback_" + Math.random().toString().replace(".", "");
|
|
try {
|
|
await new Promise((resolve, reject) => {
|
|
script.onerror = reject;
|
|
window[callback_name] = resolve;
|
|
script.src = "https://www.google.com/recaptcha/api.js?onload=" + encodeURIComponent(callback_name) + "&render=explicit";
|
|
|
|
document.body.append(script);
|
|
timeout = setTimeout(() => reject("timeout"), 15000);
|
|
});
|
|
} catch(error) {
|
|
script.remove();
|
|
script = undefined;
|
|
|
|
console.error(tr("Failed to fetch recaptcha javascript source: %o"), error);
|
|
throw tr("failed to download source");
|
|
} finally {
|
|
if(script)
|
|
script.onerror = undefined;
|
|
delete window[callback_name];
|
|
clearTimeout(timeout);
|
|
}
|
|
}
|
|
|
|
if(typeof(window.grecaptcha) === "undefined")
|
|
throw tr("failed to load recaptcha");
|
|
}
|
|
|
|
export async function spawn(container: JQuery, key: string, callback_data: (token: string) => any) {
|
|
try {
|
|
await initialize();
|
|
} catch(error) {
|
|
console.error(tr("Failed to initialize G-Recaptcha. Error: %o"), error);
|
|
throw tr("initialisation failed");
|
|
}
|
|
if(container.attr("captcha-uuid"))
|
|
window.grecaptcha.reset(container.attr("captcha-uuid"));
|
|
else {
|
|
container.attr("captcha-uuid", window.grecaptcha.render(container[0], {
|
|
"sitekey": key,
|
|
callback: callback_data
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
function api_url() {
|
|
return settings.static_global(Settings.KEY_TEAFORO_URL);
|
|
}
|
|
|
|
export class Data {
|
|
readonly auth_key: string;
|
|
readonly raw: string;
|
|
readonly sign: string;
|
|
|
|
parsed: {
|
|
user_id: number;
|
|
user_name: string;
|
|
|
|
data_age: number;
|
|
|
|
user_group_id: number;
|
|
|
|
is_staff: boolean;
|
|
user_groups: number[];
|
|
};
|
|
|
|
constructor(auth: string, raw: string, sign: string) {
|
|
this.auth_key = auth;
|
|
this.raw = raw;
|
|
this.sign = sign;
|
|
|
|
this.parsed = JSON.parse(raw);
|
|
}
|
|
|
|
|
|
data_json() : string { return this.raw; }
|
|
data_sign() : string { return this.sign; }
|
|
|
|
name() : string { return this.parsed.user_name; }
|
|
|
|
user_id() { return this.parsed.user_id; }
|
|
user_group() { return this.parsed.user_group_id; }
|
|
|
|
is_stuff() : boolean { return this.parsed.is_staff; }
|
|
is_premium() : boolean { return this.parsed.user_groups.indexOf(5) != -1; }
|
|
|
|
data_age() : Date { return new Date(this.parsed.data_age); }
|
|
|
|
is_expired() : boolean { return this.parsed.data_age + 48 * 60 * 60 * 1000 < Date.now(); }
|
|
should_renew() : boolean { return this.parsed.data_age + 24 * 60 * 60 * 1000 < Date.now(); } /* renew data all 24hrs */
|
|
}
|
|
let _data: Data | undefined;
|
|
|
|
export function logged_in() : boolean {
|
|
return !!_data && !_data.is_expired();
|
|
}
|
|
|
|
export function data() : Data { return _data; }
|
|
|
|
export interface LoginResult {
|
|
status: "success" | "captcha" | "error";
|
|
|
|
error_message?: string;
|
|
captcha?: {
|
|
type: "gre-captcha" | "unknown";
|
|
data: any; /* in case of gre-captcha it would be the side key */
|
|
};
|
|
}
|
|
|
|
export async function login(username: string, password: string, captcha?: any) : Promise<LoginResult> {
|
|
let response;
|
|
try {
|
|
response = await new Promise<any>((resolve, reject) => {
|
|
$.ajax({
|
|
url: api_url() + "?web-api/v1/login",
|
|
type: "POST",
|
|
cache: false,
|
|
data: {
|
|
username: username,
|
|
password: password,
|
|
remember: true,
|
|
"g-recaptcha-response": captcha
|
|
},
|
|
|
|
crossDomain: true,
|
|
|
|
success: resolve,
|
|
error: (xhr, status, error) => {
|
|
console.log(tr("Login request failed %o: %o"), status, error);
|
|
reject(tr("request failed"));
|
|
}
|
|
})
|
|
});
|
|
} catch(error) {
|
|
return {
|
|
status: "error",
|
|
error_message: tr("failed to send login request")
|
|
};
|
|
}
|
|
|
|
if(response["status"] !== "ok") {
|
|
console.error(tr("Response status not okey. Error happend: %o"), response);
|
|
return {
|
|
status: "error",
|
|
error_message: (response["errors"] || [])[0] || tr("Unknown error")
|
|
};
|
|
}
|
|
|
|
if(!response["success"]) {
|
|
console.error(tr("Login failed. Response %o"), response);
|
|
|
|
let message = tr("failed to login");
|
|
let captcha;
|
|
/* user/password wrong | and maybe captcha required */
|
|
if(response["code"] == 1 || response["code"] == 3)
|
|
message = tr("Invalid username or password");
|
|
if(response["code"] == 2 || response["code"] == 3) {
|
|
captcha = {
|
|
type: response["captcha"]["type"],
|
|
data: response["captcha"]["siteKey"] //TODO: Why so static here?
|
|
};
|
|
if(response["code"] == 2)
|
|
message = tr("captcha required");
|
|
}
|
|
|
|
return {
|
|
status: typeof(captcha) !== "undefined" ? "captcha" : "error",
|
|
error_message: message,
|
|
captcha: captcha
|
|
};
|
|
}
|
|
//document.cookie = "user_data=" + response["data"] + ";path=/";
|
|
//document.cookie = "user_sign=" + response["sign"] + ";path=/";
|
|
|
|
try {
|
|
_data = new Data(response["auth-key"], response["data"], response["sign"]);
|
|
localStorage.setItem("teaspeak-forum-data", response["data"]);
|
|
localStorage.setItem("teaspeak-forum-sign", response["sign"]);
|
|
localStorage.setItem("teaspeak-forum-auth", response["auth-key"]);
|
|
profiles.identities.update_forum();
|
|
} catch(error) {
|
|
console.error(tr("Failed to parse forum given data: %o"), error);
|
|
return {
|
|
status: "error",
|
|
error_message: tr("Failed to parse response data")
|
|
}
|
|
}
|
|
|
|
return {
|
|
status: "success"
|
|
};
|
|
}
|
|
|
|
export async function renew_data() : Promise<"success" | "login-required"> {
|
|
let response;
|
|
try {
|
|
response = await new Promise<any>((resolve, reject) => {
|
|
$.ajax({
|
|
url: api_url() + "?web-api/v1/renew-data",
|
|
type: "GET",
|
|
cache: false,
|
|
|
|
crossDomain: true,
|
|
|
|
data: {
|
|
"auth-key": _data.auth_key
|
|
},
|
|
|
|
success: resolve,
|
|
error: (xhr, status, error) => {
|
|
console.log(tr("Renew request failed %o: %o"), status, error);
|
|
reject(tr("request failed"));
|
|
}
|
|
})
|
|
});
|
|
} catch(error) {
|
|
throw tr("failed to send renew request");
|
|
}
|
|
|
|
if(response["status"] !== "ok") {
|
|
console.error(tr("Response status not okey. Error happend: %o"), response);
|
|
throw (response["errors"] || [])[0] || tr("Unknown error");
|
|
}
|
|
|
|
if(!response["success"]) {
|
|
if(response["code"] == 1) {
|
|
return "login-required";
|
|
}
|
|
throw "invalid error code (" + response["code"] + ")";
|
|
}
|
|
if(!response["data"] || !response["sign"])
|
|
throw tr("response missing data");
|
|
|
|
console.debug(tr("Renew succeeded. Parsing data."));
|
|
|
|
try {
|
|
_data = new Data(_data.auth_key, response["data"], response["sign"]);
|
|
localStorage.setItem("teaspeak-forum-data", response["data"]);
|
|
localStorage.setItem("teaspeak-forum-sign", response["sign"]);
|
|
profiles.identities.update_forum();
|
|
} catch(error) {
|
|
console.error(tr("Failed to parse forum given data: %o"), error);
|
|
throw tr("failed to parse data");
|
|
}
|
|
|
|
return "success";
|
|
}
|
|
|
|
export async function logout() : Promise<void> {
|
|
if(!logged_in())
|
|
return;
|
|
|
|
let response;
|
|
try {
|
|
response = await new Promise<any>((resolve, reject) => {
|
|
$.ajax({
|
|
url: api_url() + "?web-api/v1/logout",
|
|
type: "GET",
|
|
cache: false,
|
|
|
|
crossDomain: true,
|
|
|
|
data: {
|
|
"auth-key": _data.auth_key
|
|
},
|
|
|
|
success: resolve,
|
|
error: (xhr, status, error) => {
|
|
console.log(tr("Logout request failed %o: %o"), status, error);
|
|
reject(tr("request failed"));
|
|
}
|
|
})
|
|
});
|
|
} catch(error) {
|
|
throw tr("failed to send logout request");
|
|
}
|
|
|
|
if(response["status"] !== "ok") {
|
|
console.error(tr("Response status not okey. Error happend: %o"), response);
|
|
throw (response["errors"] || [])[0] || tr("Unknown error");
|
|
}
|
|
|
|
if(!response["success"]) {
|
|
/* code 1 means not logged in, its an success */
|
|
if(response["code"] != 1) {
|
|
throw "invalid error code (" + response["code"] + ")";
|
|
}
|
|
}
|
|
|
|
_data = undefined;
|
|
localStorage.removeItem("teaspeak-forum-data");
|
|
localStorage.removeItem("teaspeak-forum-sign");
|
|
localStorage.removeItem("teaspeak-forum-auth");
|
|
profiles.identities.update_forum();
|
|
}
|
|
|
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|
name: "TeaForo initialize",
|
|
priority: 10,
|
|
function: async () => {
|
|
const raw_data = localStorage.getItem("teaspeak-forum-data");
|
|
const raw_sign = localStorage.getItem("teaspeak-forum-sign");
|
|
const forum_auth = localStorage.getItem("teaspeak-forum-auth");
|
|
if(!raw_data || !raw_sign || !forum_auth) {
|
|
console.log(tr("No TeaForo authentification found. TeaForo connection status: unconnected"));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
_data = new Data(forum_auth, raw_data, raw_sign);
|
|
} catch(error) {
|
|
console.error(tr("Failed to initialize TeaForo connection from local data. Error: %o"), error);
|
|
return;
|
|
}
|
|
if(_data.should_renew()) {
|
|
console.info(tr("TeaForo data should be renewed. Executing renew."));
|
|
renew_data().then(status => {
|
|
if(status === "success") {
|
|
console.info(tr("TeaForo data has been successfully renewed."));
|
|
} else {
|
|
console.warn(tr("Failed to renew TeaForo data. New login required."));
|
|
localStorage.removeItem("teaspeak-forum-data");
|
|
localStorage.removeItem("teaspeak-forum-sign");
|
|
localStorage.removeItem("teaspeak-forum-auth");
|
|
}
|
|
}).catch(error => {
|
|
console.warn(tr("Failed to renew TeaForo data. An error occurred: %o"), error);
|
|
});
|
|
return;
|
|
}
|
|
|
|
if(_data && _data.is_expired()) {
|
|
console.error(tr("TeaForo data is expired. TeaForo connection isn't available!"));
|
|
}
|
|
}
|
|
})
|
|
} |