Implement WebAuthn (#967)

* implement webauthn

* code review

---------

Co-authored-by: Madeline <46743919+MaddyUnderStars@users.noreply.github.com>
This commit is contained in:
Puyodead1
2023-01-29 21:30:42 -05:00
committed by GitHub
parent 2a6d6b2dd2
commit 1e21802064
20 changed files with 676 additions and 47 deletions
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+1
View File
@@ -78,6 +78,7 @@
"dotenv": "^16.0.2",
"exif-be-gone": "^1.3.1",
"fast-zlib": "^2.0.1",
"fido2-lib": "^3.3.5",
"file-type": "16.5",
"form-data": "^4.0.0",
"i18next": "^21.9.2",
+8 -1
View File
@@ -19,7 +19,13 @@
import "missing-native-js-functions";
import { Server, ServerOptions } from "lambert-server";
import { Authentication, CORS } from "./middlewares/";
import { Config, initDatabase, initEvent, Sentry } from "@fosscord/util";
import {
Config,
initDatabase,
initEvent,
Sentry,
WebAuthn,
} from "@fosscord/util";
import { ErrorHandler } from "./middlewares/ErrorHandler";
import { BodyParser } from "./middlewares/BodyParser";
import { Router, Request, Response } from "express";
@@ -58,6 +64,7 @@ export class FosscordServer extends Server {
await initEvent();
await initInstance();
await Sentry.init(this.app);
WebAuthn.init();
const logRequests = process.env["LOG_REQUESTS"] != undefined;
if (logRequests) {
+1
View File
@@ -27,6 +27,7 @@ export const NO_AUTHORIZATION_ROUTES = [
"/auth/register",
"/auth/location-metadata",
"/auth/mfa/totp",
"/auth/mfa/webauthn",
// Routes with a seperate auth system
"/webhooks/",
// Public information endpoints
+49 -7
View File
@@ -16,18 +16,20 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { route, getIpAdress, verifyCaptcha } from "@fosscord/api";
import bcrypt from "bcrypt";
import { getIpAdress, route, verifyCaptcha } from "@fosscord/api";
import {
Config,
User,
generateToken,
adjustEmail,
Config,
FieldErrors,
generateToken,
generateWebAuthnTicket,
LoginSchema,
User,
WebAuthn,
} from "@fosscord/util";
import bcrypt from "bcrypt";
import crypto from "crypto";
import { Request, Response, Router } from "express";
const router: Router = Router();
export default router;
@@ -73,7 +75,10 @@ router.post(
"settings",
"totp_secret",
"mfa_enabled",
"webauthn_enabled",
"security_keys",
],
relations: ["security_keys"],
}).catch(() => {
throw FieldErrors({
login: {
@@ -116,7 +121,7 @@ router.post(
});
}
if (user.mfa_enabled) {
if (user.mfa_enabled && !user.webauthn_enabled) {
// TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy
const ticket = crypto.randomBytes(40).toString("hex");
@@ -130,6 +135,40 @@ router.post(
});
}
if (user.mfa_enabled && user.webauthn_enabled) {
if (!WebAuthn.fido2) {
// TODO: I did this for typescript and I can't use !
throw new Error("WebAuthn not enabled");
}
const options = await WebAuthn.fido2.assertionOptions();
const challenge = JSON.stringify({
publicKey: {
...options,
challenge: Buffer.from(options.challenge).toString(
"base64",
),
allowCredentials: user.security_keys.map((x) => ({
id: x.key_id,
type: "public-key",
})),
transports: ["usb", "ble", "nfc"],
timeout: 60000,
},
});
const ticket = await generateWebAuthnTicket(challenge);
await User.update({ id: user.id }, { totp_last_ticket: ticket });
return res.json({
ticket: ticket,
mfa: true,
sms: false, // TODO
token: null,
webauthn: challenge,
});
}
const token = await generateToken(user.id);
// Notice this will have a different token structure, than discord
@@ -147,6 +186,9 @@ router.post(
* MFA required:
* @returns {"token": null, "mfa": true, "sms": true, "ticket": "SOME TICKET JWT TOKEN"}
* WebAuthn MFA required:
* @returns {"token": null, "mfa": true, "webauthn": true, "sms": true, "ticket": "SOME TICKET JWT TOKEN"}
* Captcha required:
* @returns {"captcha_key": ["captcha-required"], "captcha_sitekey": null, "captcha_service": "recaptcha"}
+112
View File
@@ -0,0 +1,112 @@
/*
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Fosscord and Fosscord Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route } from "@fosscord/api";
import {
generateToken,
SecurityKey,
User,
verifyWebAuthnToken,
WebAuthn,
WebAuthnTotpSchema,
} from "@fosscord/util";
import { Request, Response, Router } from "express";
import { ExpectedAssertionResult } from "fido2-lib";
import { HTTPError } from "lambert-server";
const router = Router();
function toArrayBuffer(buf: Buffer) {
const ab = new ArrayBuffer(buf.length);
const view = new Uint8Array(ab);
for (let i = 0; i < buf.length; ++i) {
view[i] = buf[i];
}
return ab;
}
router.post(
"/",
route({ body: "WebAuthnTotpSchema" }),
async (req: Request, res: Response) => {
if (!WebAuthn.fido2) {
// TODO: I did this for typescript and I can't use !
throw new Error("WebAuthn not enabled");
}
const { code, ticket } = req.body as WebAuthnTotpSchema;
const user = await User.findOneOrFail({
where: {
totp_last_ticket: ticket,
},
select: ["id", "settings"],
});
const ret = await verifyWebAuthnToken(ticket);
if (!ret)
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
await User.update({ id: user.id }, { totp_last_ticket: "" });
const clientAttestationResponse = JSON.parse(code);
const securityKey = await SecurityKey.findOneOrFail({
where: {
user_id: req.user_id,
key_id: clientAttestationResponse.rawId,
},
});
if (!clientAttestationResponse.rawId)
throw new HTTPError("Missing rawId", 400);
clientAttestationResponse.rawId = toArrayBuffer(
Buffer.from(clientAttestationResponse.rawId, "base64"),
);
const assertionExpectations: ExpectedAssertionResult = JSON.parse(
Buffer.from(
clientAttestationResponse.response.clientDataJSON,
"base64",
).toString(),
);
const authnResult = await WebAuthn.fido2.assertionResult(
clientAttestationResponse,
{
...assertionExpectations,
factor: "second",
publicKey: securityKey.public_key,
prevCounter: securityKey.counter,
userHandle: securityKey.key_id,
},
);
const counter = authnResult.authnrData.get("counter");
securityKey.counter = counter;
await securityKey.save();
return res.json({
token: await generateToken(user.id),
user_settings: user.settings,
});
},
);
export default router;
@@ -0,0 +1,35 @@
/*
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Fosscord and Fosscord Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route } from "@fosscord/api";
import { SecurityKey } from "@fosscord/util";
import { Request, Response, Router } from "express";
const router = Router();
router.delete("/", route({}), async (req: Request, res: Response) => {
const { key_id } = req.params;
await SecurityKey.delete({
id: key_id,
user_id: req.user_id,
});
res.sendStatus(204);
});
export default router;
@@ -0,0 +1,196 @@
/*
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Fosscord and Fosscord Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route } from "@fosscord/api";
import {
CreateWebAuthnCredentialSchema,
DiscordApiErrors,
FieldErrors,
GenerateWebAuthnCredentialsSchema,
generateWebAuthnTicket,
SecurityKey,
User,
verifyWebAuthnToken,
WebAuthn,
WebAuthnPostSchema,
} from "@fosscord/util";
import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
import { ExpectedAttestationResult } from "fido2-lib";
import { HTTPError } from "lambert-server";
const router = Router();
const isGenerateSchema = (
body: WebAuthnPostSchema,
): body is GenerateWebAuthnCredentialsSchema => {
return "password" in body;
};
const isCreateSchema = (
body: WebAuthnPostSchema,
): body is CreateWebAuthnCredentialSchema => {
return "credential" in body;
};
function toArrayBuffer(buf: Buffer) {
const ab = new ArrayBuffer(buf.length);
const view = new Uint8Array(ab);
for (let i = 0; i < buf.length; ++i) {
view[i] = buf[i];
}
return ab;
}
router.get("/", route({}), async (req: Request, res: Response) => {
const securityKeys = await SecurityKey.find({
where: {
user_id: req.user_id,
},
});
return res.json(
securityKeys.map((key) => ({
id: key.id,
name: key.name,
})),
);
});
router.post(
"/",
route({ body: "WebAuthnPostSchema" }),
async (req: Request, res: Response) => {
if (!WebAuthn.fido2) {
// TODO: I did this for typescript and I can't use !
throw new Error("WebAuthn not enabled");
}
const user = await User.findOneOrFail({
where: {
id: req.user_id,
},
select: [
"data",
"id",
"disabled",
"deleted",
"settings",
"totp_secret",
"mfa_enabled",
"username",
],
});
if (isGenerateSchema(req.body)) {
const { password } = req.body;
const same_password = await bcrypt.compare(
password,
user.data.hash || "",
);
if (!same_password) {
throw FieldErrors({
password: {
message: req.t("auth:login.INVALID_PASSWORD"),
code: "INVALID_PASSWORD",
},
});
}
const registrationOptions =
await WebAuthn.fido2.attestationOptions();
const challenge = JSON.stringify({
publicKey: {
...registrationOptions,
challenge: Buffer.from(
registrationOptions.challenge,
).toString("base64"),
user: {
id: user.id,
name: user.username,
displayName: user.username,
},
},
});
const ticket = await generateWebAuthnTicket(challenge);
return res.json({
ticket: ticket,
challenge,
});
} else if (isCreateSchema(req.body)) {
const { credential, name, ticket } = req.body;
const verified = await verifyWebAuthnToken(ticket);
if (!verified) throw new HTTPError("Invalid ticket", 400);
const clientAttestationResponse = JSON.parse(credential);
if (!clientAttestationResponse.rawId)
throw new HTTPError("Missing rawId", 400);
const rawIdBuffer = Buffer.from(
clientAttestationResponse.rawId,
"base64",
);
clientAttestationResponse.rawId = toArrayBuffer(rawIdBuffer);
const attestationExpectations: ExpectedAttestationResult =
JSON.parse(
Buffer.from(
clientAttestationResponse.response.clientDataJSON,
"base64",
).toString(),
);
const regResult = await WebAuthn.fido2.attestationResult(
clientAttestationResponse,
{
...attestationExpectations,
factor: "second",
},
);
const authnrData = regResult.authnrData;
const keyId = Buffer.from(authnrData.get("credId")).toString(
"base64",
);
const counter = authnrData.get("counter");
const publicKey = authnrData.get("credentialPublicKeyPem");
const securityKey = SecurityKey.create({
name,
counter,
public_key: publicKey,
user_id: req.user_id,
key_id: keyId,
});
await securityKey.save();
return res.json({
name,
id: securityKey.id,
});
} else {
throw DiscordApiErrors.INVALID_AUTHENTICATION_TOKEN;
}
},
);
export default router;
+46
View File
@@ -0,0 +1,46 @@
/*
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Fosscord and Fosscord Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { User } from "./User";
@Entity("security_keys")
export class SecurityKey extends BaseClass {
@Column({ nullable: true })
@RelationId((key: SecurityKey) => key.user)
user_id: string;
@JoinColumn({ name: "user_id" })
@ManyToOne(() => User, {
onDelete: "CASCADE",
})
user: User;
@Column()
key_id: string;
@Column()
public_key: string;
@Column()
counter: number;
@Column()
name: string;
}
+7
View File
@@ -33,6 +33,7 @@ import { UserSettings } from "./UserSettings";
import { Session } from "./Session";
import { Config, FieldErrors, Snowflake, trimSpecial, adjustEmail } from "..";
import { Request } from "express";
import { SecurityKey } from "./SecurityKey";
export enum PublicUserEnum {
username,
@@ -138,6 +139,9 @@ export class User extends BaseClass {
@Column({ select: false })
mfa_enabled: boolean = false; // if multi factor authentication is enabled
@Column({ select: false, default: false })
webauthn_enabled: boolean = false; // if webauthn multi factor authentication is enabled
@Column({ select: false, nullable: true })
totp_secret?: string = "";
@@ -223,6 +227,9 @@ export class User extends BaseClass {
@Column({ type: "simple-json", select: false })
extended_settings: string = "{}";
@OneToMany(() => SecurityKey, (key: SecurityKey) => key.user)
security_keys: SecurityKey[];
// TODO: I don't like this method?
validate() {
if (this.email) {
+2 -1
View File
@@ -23,8 +23,8 @@ export * from "./BackupCodes";
export * from "./Ban";
export * from "./BaseClass";
export * from "./Categories";
export * from "./ClientRelease";
export * from "./Channel";
export * from "./ClientRelease";
export * from "./Config";
export * from "./ConnectedAccount";
export * from "./EmbedCache";
@@ -41,6 +41,7 @@ export * from "./ReadState";
export * from "./Recipient";
export * from "./Relationship";
export * from "./Role";
export * from "./SecurityKey";
export * from "./Session";
export * from "./Sticker";
export * from "./StickerPack";
@@ -0,0 +1,27 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class webauthn1675045120206 implements MigrationInterface {
name = "webauthn1675045120206";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE \`security_keys\` (\`id\` varchar(255) NOT NULL, \`user_id\` varchar(255) NULL, \`key_id\` varchar(255) NOT NULL, \`public_key\` varchar(255) NOT NULL, \`counter\` int NOT NULL, \`name\` varchar(255) NOT NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`ALTER TABLE \`users\` ADD \`webauthn_enabled\` tinyint NOT NULL DEFAULT 0`,
);
await queryRunner.query(
`ALTER TABLE \`security_keys\` ADD CONSTRAINT \`FK_24c97d0771cafedce6d7163eaad\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE \`security_keys\` DROP FOREIGN KEY \`FK_24c97d0771cafedce6d7163eaad\``,
);
await queryRunner.query(
`ALTER TABLE \`users\` DROP COLUMN \`webauthn_enabled\``,
);
await queryRunner.query(`DROP TABLE \`security_keys\``);
}
}
@@ -0,0 +1,27 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class webauthn1675045120206 implements MigrationInterface {
name = "webauthn1675045120206";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE \`security_keys\` (\`id\` varchar(255) NOT NULL, \`user_id\` varchar(255) NULL, \`key_id\` varchar(255) NOT NULL, \`public_key\` varchar(255) NOT NULL, \`counter\` int NOT NULL, \`name\` varchar(255) NOT NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`ALTER TABLE \`users\` ADD \`webauthn_enabled\` tinyint NOT NULL DEFAULT 0`,
);
await queryRunner.query(
`ALTER TABLE \`security_keys\` ADD CONSTRAINT \`FK_24c97d0771cafedce6d7163eaad\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE \`security_keys\` DROP FOREIGN KEY \`FK_24c97d0771cafedce6d7163eaad\``,
);
await queryRunner.query(
`ALTER TABLE \`users\` DROP COLUMN \`webauthn_enabled\``,
);
await queryRunner.query(`DROP TABLE \`security_keys\``);
}
}
@@ -0,0 +1,27 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class webauthn1675044825710 implements MigrationInterface {
name = "webauthn1675044825710";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "security_keys" ("id" character varying NOT NULL, "user_id" character varying, "key_id" character varying NOT NULL, "public_key" character varying NOT NULL, "counter" integer NOT NULL, "name" character varying NOT NULL, CONSTRAINT "PK_6e95cdd91779e7cca06d1fff89c" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`ALTER TABLE "users" ADD "webauthn_enabled" boolean NOT NULL DEFAULT false`,
);
await queryRunner.query(
`ALTER TABLE "security_keys" ADD CONSTRAINT "FK_24c97d0771cafedce6d7163eaad" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "security_keys" DROP CONSTRAINT "FK_24c97d0771cafedce6d7163eaad"`,
);
await queryRunner.query(
`ALTER TABLE "users" DROP COLUMN "webauthn_enabled"`,
);
await queryRunner.query(`DROP TABLE "security_keys"`);
}
}
+38
View File
@@ -0,0 +1,38 @@
/*
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Fosscord and Fosscord Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// FIXME: better naming
export interface GenerateWebAuthnCredentialsSchema {
password: string;
}
// FIXME: better naming
export interface CreateWebAuthnCredentialSchema {
credential: string;
name: string;
ticket: string;
}
export type WebAuthnPostSchema = Partial<
GenerateWebAuthnCredentialsSchema | CreateWebAuthnCredentialSchema
>;
export interface WebAuthnTotpSchema {
code: string;
ticket: string;
}
+31 -38
View File
@@ -16,66 +16,59 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./Validator";
export * from "./SelectProtocolSchema";
export * from "./LoginSchema";
export * from "./RegisterSchema";
export * from "./TotpSchema";
export * from "./ActivitySchema";
export * from "./ApplicationAuthorizeSchema";
export * from "./ApplicationCreateSchema";
export * from "./ApplicationModifySchema";
export * from "./BackupCodesChallengeSchema";
export * from "./ChannelModifySchema";
export * from "./InviteCreateSchema";
export * from "./PurgeSchema";
export * from "./WebhookCreateSchema";
export * from "./MessageCreateSchema";
export * from "./MessageAcknowledgeSchema";
export * from "./GuildCreateSchema";
export * from "./BanCreateSchema";
export * from "./BanModeratorSchema";
export * from "./BanRegistrySchema";
export * from "./BotModifySchema";
export * from "./ChannelModifySchema";
export * from "./ChannelPermissionOverwriteSchema";
export * from "./ChannelReorderSchema";
export * from "./CodesVerificationSchema";
export * from "./DmChannelCreateSchema";
export * from "./EmojiCreateSchema";
export * from "./EmojiModifySchema";
export * from "./ModifyGuildStickerSchema";
export * from "./TemplateCreateSchema";
export * from "./TemplateModifySchema";
export * from "./VanityUrlSchema";
export * from "./GatewayPayloadSchema";
export * from "./GuildCreateSchema";
export * from "./GuildTemplateCreateSchema";
export * from "./GuildUpdateSchema";
export * from "./GuildUpdateWelcomeScreenSchema";
export * from "./WidgetModifySchema";
export * from "./IdentifySchema";
export * from "./InviteCreateSchema";
export * from "./LazyRequestSchema";
export * from "./LoginSchema";
export * from "./MemberChangeProfileSchema";
export * from "./MemberChangeSchema";
export * from "./RoleModifySchema";
export * from "./GuildTemplateCreateSchema";
export * from "./DmChannelCreateSchema";
export * from "./UserModifySchema";
export * from "./MessageAcknowledgeSchema";
export * from "./MessageCreateSchema";
export * from "./MfaCodesSchema";
export * from "./ModifyGuildStickerSchema";
export * from "./PurgeSchema";
export * from "./RegisterSchema";
export * from "./RelationshipPostSchema";
export * from "./RelationshipPutSchema";
export * from "./CodesVerificationSchema";
export * from "./MfaCodesSchema";
export * from "./RoleModifySchema";
export * from "./RolePositionUpdateSchema";
export * from "./SelectProtocolSchema";
export * from "./TemplateCreateSchema";
export * from "./TemplateModifySchema";
export * from "./TotpDisableSchema";
export * from "./TotpEnableSchema";
export * from "./VoiceIdentifySchema";
export * from "./TotpSchema";
export * from "./UserDeleteSchema";
export * from "./UserGuildSettingsSchema";
export * from "./UserModifySchema";
export * from "./UserProfileModifySchema";
export * from "./UserSettingsSchema";
export * from "./Validator";
export * from "./VanityUrlSchema";
export * from "./VoiceIdentifySchema";
export * from "./VoiceStateUpdateSchema";
export * from "./VoiceVideoSchema";
export * from "./IdentifySchema";
export * from "./ActivitySchema";
export * from "./LazyRequestSchema";
export * from "./GuildUpdateSchema";
export * from "./ChannelPermissionOverwriteSchema";
export * from "./UserGuildSettingsSchema";
export * from "./GatewayPayloadSchema";
export * from "./RolePositionUpdateSchema";
export * from "./ChannelReorderSchema";
export * from "./UserSettingsSchema";
export * from "./BotModifySchema";
export * from "./ApplicationModifySchema";
export * from "./ApplicationCreateSchema";
export * from "./ApplicationAuthorizeSchema";
export * from "./WebAuthnSchema";
export * from "./WebhookCreateSchema";
export * from "./WidgetModifySchema";
+68
View File
@@ -0,0 +1,68 @@
/*
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Fosscord and Fosscord Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Fido2Lib } from "fido2-lib";
import jwt from "jsonwebtoken";
import { Config } from "./Config";
const JWTOptions: jwt.SignOptions = {
algorithm: "HS256",
expiresIn: "5m",
};
export const WebAuthn: {
fido2: Fido2Lib | null;
init: () => void;
} = {
fido2: null,
init: function () {
this.fido2 = new Fido2Lib({
challengeSize: 128,
});
},
};
export async function generateWebAuthnTicket(
challenge: string,
): Promise<string> {
return new Promise((res, rej) => {
jwt.sign(
{ challenge },
Config.get().security.jwtSecret,
JWTOptions,
(err, token) => {
if (err || !token) return rej(err || "no token");
return res(token);
},
);
});
}
export async function verifyWebAuthnToken(token: string) {
return new Promise((res, rej) => {
jwt.verify(
token,
Config.get().security.jwtSecret,
JWTOptions,
async (err, decoded) => {
if (err) return rej(err);
return res(decoded);
},
);
});
}
+1
View File
@@ -39,3 +39,4 @@ export * from "./Array";
export * from "./TraverseDirectory";
export * from "./InvisibleCharacters";
export * from "./Sentry";
export * from "./WebAuthn";