-
Notifications
You must be signed in to change notification settings - Fork 61
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #301 from daanbreur/300-account-creation-from-discord
Add linked account creation from the Discord bot
- Loading branch information
Showing
9 changed files
with
369 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
ALTER TABLE ctfnote_private.invitation_link | ||
ADD COLUMN "discord_id" TEXT UNIQUE DEFAULT NULL; | ||
|
||
DROP FUNCTION ctfnote.create_invitation_link ("role" ctfnote.role); | ||
CREATE OR REPLACE FUNCTION ctfnote.create_invitation_link ("role" ctfnote.role, "discord_id" text default null) | ||
RETURNS ctfnote.invitation_link_response | ||
AS $$ | ||
DECLARE | ||
invitation_link ctfnote_private.invitation_link; | ||
BEGIN | ||
INSERT INTO ctfnote_private.invitation_link ("role", "token", "discord_id") | ||
VALUES (create_invitation_link.role, gen_random_uuid (), create_invitation_link.discord_id) | ||
RETURNING | ||
* INTO invitation_link; | ||
RETURN ROW (invitation_link.token::text)::ctfnote.invitation_link_response; | ||
END; | ||
$$ | ||
LANGUAGE plpgsql | ||
SECURITY DEFINER; | ||
|
||
GRANT EXECUTE ON FUNCTION ctfnote.create_invitation_link (ctfnote.role, text) TO user_admin; | ||
|
||
CREATE OR REPLACE FUNCTION ctfnote.register_with_token ("token" text, "login" text, "password" text) | ||
RETURNS ctfnote.jwt | ||
AS $$ | ||
DECLARE | ||
invitation_role ctfnote.role; | ||
invitation_discord_id text; | ||
BEGIN | ||
SELECT | ||
ROLE, discord_id INTO invitation_role, invitation_discord_id | ||
FROM | ||
ctfnote_private.invitation_link | ||
WHERE | ||
invitation_link.token::text = register_with_token.token | ||
AND expiration > now(); | ||
IF invitation_role IS NOT NULL THEN | ||
DELETE FROM ctfnote_private.invitation_link | ||
WHERE invitation_link.token::text = register_with_token.token; | ||
IF invitation_discord_id IS NOT NULL THEN | ||
RETURN ctfnote_private.do_register (register_with_token.login, register_with_token.password, invitation_role, invitation_discord_id); | ||
ELSE | ||
RETURN ctfnote_private.do_register (register_with_token.login, register_with_token.password, invitation_role); | ||
END IF; | ||
ELSE | ||
RAISE EXCEPTION 'Invalid token'; | ||
END IF; | ||
END | ||
$$ | ||
LANGUAGE plpgsql | ||
SECURITY DEFINER; | ||
|
||
GRANT EXECUTE ON FUNCTION ctfnote.register_with_token (text, text, text) TO user_anonymous; | ||
|
||
-- first we remove and re-apply the old internal registration function to be extra verbose | ||
-- we implement the additional logic for registration with discord_id in a seperate function with the same name, thus overloading this function for normal original operation and | ||
-- operation with the new discord id linking. | ||
DROP FUNCTION ctfnote_private.do_register ("login" text, "password" text, "role" ctfnote.role); | ||
|
||
CREATE OR REPLACE FUNCTION ctfnote_private.do_register ("login" text, "password" text, "role" ctfnote.role) | ||
RETURNS ctfnote.jwt | ||
AS $$ | ||
DECLARE | ||
new_user ctfnote_private.user; | ||
BEGIN | ||
INSERT INTO ctfnote_private.user ("login", "password", "role") | ||
VALUES (do_register.login, crypt(do_register.password, gen_salt('bf')), do_register.role) | ||
RETURNING | ||
* INTO new_user; | ||
INSERT INTO ctfnote.profile ("id", "username") | ||
VALUES (new_user.id, do_register.login); | ||
RETURN (ctfnote_private.new_token (new_user.id))::ctfnote.jwt; | ||
EXCEPTION | ||
WHEN unique_violation THEN | ||
RAISE EXCEPTION 'Username already taken'; | ||
END; | ||
$$ | ||
LANGUAGE plpgsql | ||
STRICT | ||
SECURITY DEFINER; | ||
|
||
-- overloaded function, implements the logic needed for discord linking. | ||
CREATE OR REPLACE FUNCTION ctfnote_private.do_register ("login" text, "password" text, "role" ctfnote.role, "discord_id" text) | ||
RETURNS ctfnote.jwt | ||
AS $$ | ||
DECLARE | ||
new_user ctfnote_private.user; | ||
BEGIN | ||
INSERT INTO ctfnote_private.user ("login", "password", "role") | ||
VALUES (do_register.login, crypt(do_register.password, gen_salt('bf')), do_register.role) | ||
RETURNING | ||
* INTO new_user; | ||
INSERT INTO ctfnote.profile ("id", "username", "discord_id") | ||
VALUES (new_user.id, do_register.login, do_register.discord_id); | ||
RETURN (ctfnote_private.new_token (new_user.id))::ctfnote.jwt; | ||
EXCEPTION | ||
WHEN unique_violation THEN | ||
RAISE EXCEPTION 'Username already taken'; | ||
END; | ||
$$ | ||
LANGUAGE plpgsql | ||
STRICT | ||
SECURITY DEFINER; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import { | ||
ApplicationCommandType, | ||
Client, | ||
CommandInteraction, | ||
GuildMemberRoleManager, | ||
} from "discord.js"; | ||
import { Command } from "../command"; | ||
import { | ||
AllowedRoles, | ||
createInvitationTokenForDiscordId, | ||
getInvitationTokenForDiscordId, | ||
getUserByDiscordId, | ||
} from "../database/users"; | ||
import config from "../../config"; | ||
|
||
async function getInvitationUrl(invitationCode: string | null = null) { | ||
if (config.pad.domain == "") return null; | ||
if (invitationCode == null) return null; | ||
|
||
const ssl = config.pad.useSSL == "false" ? "" : "s"; | ||
|
||
return `http${ssl}://${config.pad.domain}/#/auth/register/${invitationCode}`; | ||
} | ||
|
||
async function getProfileUrl() { | ||
if (config.pad.domain == "") return null; | ||
|
||
const ssl = config.pad.useSSL == "false" ? "" : "s"; | ||
|
||
return `http${ssl}://${config.pad.domain}/#/user/settings`; | ||
} | ||
|
||
async function registerLogic(client: Client, interaction: CommandInteraction) { | ||
if (config.discord.registrationEnabled.toLowerCase() !== "true") { | ||
await interaction.editReply({ | ||
content: | ||
"The functionality to create your own account this way has been disabled by an administrator.", | ||
}); | ||
return; | ||
} | ||
|
||
if (config.discord.registrationRoleId !== "") { | ||
if ( | ||
!(interaction.member?.roles as GuildMemberRoleManager).cache.has( | ||
config.discord.registrationRoleId | ||
) | ||
) { | ||
await interaction.editReply({ | ||
content: | ||
"You do not have the role required to create an account yourself.", | ||
}); | ||
return; | ||
} | ||
} | ||
|
||
const userId = await getUserByDiscordId(interaction.user.id); | ||
if (userId != null) { | ||
await interaction.editReply({ | ||
content: | ||
"You can't link the same Discord account twice! If you do not have a CTFNote account or haven't linked it, contact an administrator.", | ||
}); | ||
return; | ||
} | ||
|
||
const existingInvitationCode = await getInvitationTokenForDiscordId( | ||
interaction.user.id | ||
); | ||
if (existingInvitationCode != null) { | ||
const invitationUrl = await getInvitationUrl(existingInvitationCode); | ||
if (invitationUrl == null) { | ||
await interaction.editReply({ | ||
content: | ||
"Could not generate invitation URL. Please contact an administrator.", | ||
}); | ||
return; | ||
} | ||
|
||
await interaction.editReply({ | ||
content: `Your personal invitation url: ${invitationUrl}.\n-# If you already have a CTFNote account you should link it using the \`/link\` command using the Discord token from your profile: ${await getProfileUrl()}.`, | ||
}); | ||
return; | ||
} | ||
|
||
await interaction.editReply({ | ||
content: | ||
"Generating a private invitation URL... If you already have a CTFNote account you should link it using the `/link` command instead.", | ||
}); | ||
|
||
const invitationCode = await createInvitationTokenForDiscordId( | ||
interaction.user.id, | ||
(config.discord.registrationAccountRole as AllowedRoles) ?? | ||
AllowedRoles.user_guest | ||
); | ||
|
||
if (invitationCode == null) { | ||
await interaction.editReply({ | ||
content: | ||
"Could not generate an invitation code. Please contact an administrator.", | ||
}); | ||
return; | ||
} | ||
|
||
const invitationUrl = await getInvitationUrl(invitationCode); | ||
if (invitationUrl == null) { | ||
await interaction.editReply({ | ||
content: | ||
"Could not get an invitation URL. Please contact an administrator.", | ||
}); | ||
return; | ||
} | ||
|
||
await interaction.editReply({ | ||
content: `Your personal invitation url: ${invitationUrl}.\n-# If you already have a CTFNote account you should link it using the \`/link\` command using the Discord token from your profile: ${await getProfileUrl()}.`, | ||
}); | ||
return; | ||
} | ||
|
||
export const Register: Command = { | ||
name: "register", | ||
description: "Create an account on CTFNote (if enabled)!", | ||
type: ApplicationCommandType.ChatInput, | ||
run: async (client, interaction) => { | ||
return registerLogic(client, interaction).catch((e) => { | ||
console.error("Error during /register Discord logic: ", e); | ||
}); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.