feat: implement push, account, mfa helpers

This commit is contained in:
Paul Makles
2023-05-16 15:55:01 +01:00
parent e3c7025bd1
commit eb2650a715
4 changed files with 349 additions and 10 deletions
+4 -10
View File
@@ -1,7 +1,7 @@
import { Accessor, Setter, createSignal } from "solid-js";
import EventEmitter from "eventemitter3";
import { API, DataCreateAccount, Role } from "revolt-api";
import { API, Role } from "revolt-api";
import type { DataLogin, RevoltConfig } from "revolt-api";
import { Channel, Emoji, Message, Server, ServerMember, User } from "./classes";
@@ -16,6 +16,7 @@ import {
SessionCollection,
UserCollection,
} from "./collections";
import { AccountCollection } from "./collections/AccountCollection";
import {
ConnectionState,
EventClient,
@@ -143,6 +144,7 @@ export type ClientOptions = Partial<EventClientOptions> & {
* Revolt.js Clients
*/
export class Client extends EventEmitter<Events> {
readonly account;
readonly bots;
readonly channels;
readonly channelUnreads;
@@ -213,6 +215,7 @@ export class Client extends EventEmitter<Events> {
this.connectionFailureCount = connectionFailureCount;
this.#setConnectionFailureCount = setConnectionFailureCount;
this.account = new AccountCollection(this);
this.bots = new BotCollection(this);
this.channels = new ChannelCollection(this);
this.channelUnreads = new ChannelUnreadCollection(this);
@@ -334,15 +337,6 @@ export class Client extends EventEmitter<Events> {
this.connect();
}
/**
* Register for a new account
* @param data Registration data object
* @returns A promise containing a registration response object
*/
register(data: DataCreateAccount) {
return this.api.post("/auth/account/create", data);
}
/**
* Prepare a markdown-based message to be displayed to the user as plain text.
* @param source Source markdown text
+171
View File
@@ -0,0 +1,171 @@
import { SetStoreFunction, createStore } from "solid-js/store";
import {
MFAResponse,
MultiFactorStatus,
MFATicket as TicketType,
} from "revolt-api";
import { Client } from "..";
/**
* Multi-Factor Authentication
*/
export class MFA {
#client: Client;
#store: [MultiFactorStatus, SetStoreFunction<MultiFactorStatus>];
/**
* Construct MFA helper
* @param client Client
* @param state State
*/
constructor(client: Client, state: MultiFactorStatus) {
this.#client = client;
this.#store = createStore(state);
}
/**
* Whether authenticator app is enabled
*/
get authenticatorEnabled() {
return this.#store[0].totp_mfa;
}
/**
* Whether recovery codes are enabled
*/
get recoveryEnabled() {
return this.#store[0].recovery_active;
}
/**
* Create an MFA ticket
* @param params
* @returns Token
*/
createTicket(params: MFAResponse) {
return this.#client.api
.put("/auth/mfa/ticket", params)
.then((ticket) => new MFATicket(this.#client, ticket));
}
/**
* Enable authenticator using token generated from secret found earlier
* @param token Token
*/
enableAuthenticator(token: string) {
return this.#client.api.put("/auth/mfa/totp", { totp_code: token });
}
}
/**
* MFA Ticket
*/
export class MFATicket {
#client: Client;
#ticket: TicketType;
#used = false;
/**
* Construct MFA Ticket
* @param client Client
* @param ticket Ticket
*/
constructor(client: Client, ticket: TicketType) {
this.#client = client;
this.#ticket = ticket;
}
/**
* Token
*/
get token() {
return this.#ticket.token;
}
/**
* Use the ticket
*/
#consume() {
if (this.#used) throw "Already used this ticket!";
this.#used = true;
}
/**
* Fetch recovery codes
* @returns List of codes
*/
fetchRecoveryCodes() {
this.#consume();
return this.#client.api.post("/auth/mfa/recovery", undefined, {
headers: {
"X-MFA-Ticket": this.token,
},
});
}
/**
* Generate new set of recovery codes
* @returns List of codes
*/
generateRecoveryCodes() {
this.#consume();
return this.#client.api.patch("/auth/mfa/recovery", undefined, {
headers: {
"X-MFA-Ticket": this.token,
},
});
}
/**
* Generate new authenticator secret
* @returns Secret
*/
generateAuthenticatorSecret() {
this.#consume();
return this.#client.api
.post("/auth/mfa/totp", undefined, {
headers: {
"X-MFA-Ticket": this.token,
},
})
.then((response) => response.secret);
}
/**
* Disable authenticator
*/
disableAuthenticator() {
this.#consume();
return this.#client.api.delete("/auth/mfa/totp", undefined, {
headers: {
"X-MFA-Ticket": this.token,
},
});
}
/**
* Disable account
*/
disableAccount() {
this.#consume();
return this.#client.api.post("/auth/account/disable", undefined, {
headers: {
"X-MFA-Ticket": this.token,
},
});
}
/**
* Delete account
*/
deleteAccount() {
this.#consume();
return this.#client.api.post("/auth/account/delete", undefined, {
headers: {
"X-MFA-Ticket": this.token,
},
});
}
}
+164
View File
@@ -0,0 +1,164 @@
import { DataCreateAccount, WebPushSubscription } from "revolt-api";
import { Client } from "..";
import { MFA } from "../classes/MFA";
/**
* Utility functions for working with accounts
*/
export class AccountCollection {
readonly client: Client;
/**
* Create generic class collection
* @param client Client
*/
constructor(client: Client) {
this.client = client;
}
/**
* Fetch current account email
* @returns Email
*/
fetchEmail() {
return this.client.api
.get("/auth/account/")
.then((account) => account.email);
}
/**
* Create a MFA helper
*/
async mfa() {
const state = await this.client.api.get("/auth/mfa/");
return new MFA(this.client, state);
}
/**
* Create a new account
* @param data Account details
*/
create(data: DataCreateAccount) {
return this.client.api.post("/auth/account/create", data);
}
/**
* Resend email verification
* @param email Email
* @param captcha Captcha if enabled
*/
reverify(email: string, captcha?: string) {
return this.client.api.post("/auth/account/reverify", { email, captcha });
}
/**
* Send password reset email
* @param email Email
* @param captcha Captcha if enabled
*/
resetPassword(email: string, captcha?: string) {
return this.client.api.post("/auth/account/reset_password", {
email,
captcha,
});
}
/**
* Verify an account given the code
* @param code Verification code
*/
verify(code: string) {
return this.client.api.post(`/auth/account/verify/${code}`);
}
/**
* Confirm account deletion
* @param token Deletion token
*/
confirmDelete(token: string) {
return this.client.api.put("/auth/account/delete", { token });
}
/**
* Confirm password reset
* @param token Token
* @param newPassword New password
* @param removeSessions Whether to remove existing sessions
*/
confirmPasswordReset(
token: string,
newPassword: string,
removeSessions: boolean
) {
return this.client.api.patch("/auth/account/reset_password", {
token,
password: newPassword,
remove_sessions: removeSessions,
});
}
/**
* Change account password
* @param newPassword New password
* @param currentPassword Current password
*/
changePassword(newPassword: string, currentPassword: string) {
return this.client.api.patch("/auth/account/change/password", {
password: newPassword,
current_password: currentPassword,
});
}
/**
* Change account email
* @param newEmail New email
* @param currentPassword Current password
*/
changeEmail(newEmail: string, currentPassword: string) {
return this.client.api.patch("/auth/account/change/email", {
email: newEmail,
current_password: currentPassword,
});
}
/**
* Fetch settings
* @param keys Keys
* @returns Settings
*/
fetchSettings(keys: string[]) {
return this.client.api.post("/sync/settings/fetch", { keys }) as Promise<
Record<string, [number, string]>
>;
}
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Set settings
* @param settings Settings
* @param timestamp Timestamp
*/
setSettings(settings: Record<string, any>, timestamp = +new Date()) {
return this.client.api.post("/sync/settings/set", {
...settings,
timestamp,
});
}
/* eslint-enable @typescript-eslint/no-explicit-any */
/**
* Create a new Web Push subscription
* @param subscription Subscription
*/
webPushSubscribe(subscription: WebPushSubscription) {
return this.client.api.post("/push/subscribe", subscription);
}
/**
* Remove existing Web Push subscription
*/
webPushUnsubscribe() {
return this.client.api.post("/push/unsubscribe");
}
}
+10
View File
@@ -20,6 +20,16 @@ export class SessionCollection extends ClassCollection<
return data.map((session) => this.getOrCreate(session._id, session));
}
/**
* Delete all sessions, optionally including self
* @param revokeSelf Whether to remove current session too
*/
async deleteAll(revokeSelf = false) {
await this.client.api.delete("/auth/session/all", {
revoke_self: revokeSelf,
});
}
/**
* Get or create
* @param id Id