From 78fa87737cd32f4f2f5840cbb773a95e85a2a898 Mon Sep 17 00:00:00 2001 From: EEKIM10 Date: Tue, 4 Oct 2022 18:21:44 +0100 Subject: [PATCH] Update verification flow --- cogs/verify.py | 85 ++------------------------ utils/__init__.py | 1 + utils/_email.py | 10 ++- utils/views.py | 151 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 83 deletions(-) create mode 100644 utils/views.py diff --git a/cogs/verify.py b/cogs/verify.py index ae9e562..39d5cef 100644 --- a/cogs/verify.py +++ b/cogs/verify.py @@ -2,7 +2,7 @@ import discord import orm import re from discord.ext import commands -from utils import send_verification_code, VerifyCode, Student, console, get_or_none, BannedStudentID +from utils import VerifyCode, Student, VerifyView import config @@ -11,11 +11,9 @@ class VerifyCog(commands.Cog): self.bot = bot @commands.slash_command() - async def verify(self, ctx: discord.ApplicationContext, *, code: str = None): + @discord.guild_only() + async def verify(self, ctx: discord.ApplicationContext): """Verifies or generates a verification code""" - guild = self.bot.get_guild(config.guilds[0]) - # if ctx.guild is not None: - # return await ctx.respond("\N{cross mark} This command can only be run in my DMs!", ephemeral=True) try: student: Student = await Student.objects.get(user_id=ctx.author.id) @@ -23,81 +21,8 @@ class VerifyCog(commands.Cog): except orm.NoMatch: pass - if code is None: - - class Modal(discord.ui.Modal): - def __init__(self): - super().__init__( - discord.ui.InputText( - custom_id="student_id", - label="What is your student ID", - placeholder="B...", - min_length=7, - max_length=7, - ), - title="Enter your student ID number", - ) - - async def callback(self, interaction: discord.Interaction): - await interaction.response.defer() - st = self.children[0].value - if not st: # timed out - return - - if not re.match(r"^B\d{6}$", st): - return await interaction.response.send_message( - "\N{cross mark} Invalid student ID.", - delete_after=60 - ) - - ex = await get_or_none(Student, id=st) - if ex: - return await interaction.response.send_message( - "\N{cross mark} Student ID is already associated.", - delete_after=60 - ) - - _code = await send_verification_code(ctx.author, st) - console.log(f"Sending verification email to {ctx.author} ({ctx.author.id}/{st})...") - __code = await VerifyCode.objects.create(code=_code, bind=ctx.author.id, student_id=st) - console.log(f"[green]Sent verification email to {ctx.author} ({ctx.author.id}/{st}): {_code!r}") - await interaction.followup.send( - "\N{white heavy check mark} Verification email sent to your college email " - f"({st}@my.leedscitycollege.ac.uk)\n" - f"Once you get that email, run this command again, with the first option being the 16" - f" character code.\n\n" - f">>> If you don't know how to access your email, go to , then " - f"sign in as `{st}@leedscitycollege.ac.uk` (notice there's no `my.` " - f"prefix to sign into gmail), and you should be greeted by your inbox. The default password " - f"is your birthday, !, and the first three letters of your first or last name" - f" (for example, `John Doe`, born on the 1st of february 2006, would be either " - f"`01022006!Joh` or `01022006!Doe`).", - ephemeral=True, - ) - - return await ctx.send_modal(Modal()) - else: - try: - existing: VerifyCode = await VerifyCode.objects.get(code=code) - except orm.NoMatch: - return await ctx.respond( - "\N{cross mark} Invalid or unknown verification code. Try again!", ephemeral=True - ) - else: - ban = await get_or_none(BannedStudentID, student_id=existing.student_id) - if ban is not None: - return await ctx.author.ban( - reason=f"Attempted to verify with banned student ID {ban.student_id}" - f" (originally associated with account {ban.associated_account})" - ) - await Student.objects.create(id=existing.student_id, user_id=ctx.author.id) - await existing.delete() - role = discord.utils.find(lambda r: r.name.lower() == "verified", guild.roles) - if role and role < guild.me.top_role: - member = await guild.fetch_member(ctx.author.id) - await member.add_roles(role, reason="Verified") - console.log(f"[green]{ctx.author} verified ({ctx.author.id}/{existing.student_id})") - return await ctx.respond("\N{white heavy check mark} Verification complete!", ephemeral=True) + view = VerifyView(ctx) + return await ctx.respond(view=view, ephemeral=True) @commands.command(name="de-verify") @commands.is_owner() diff --git a/utils/__init__.py b/utils/__init__.py index 350a2a3..8451b88 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,3 +1,4 @@ from ._email import * from .db import * from .console import * +from .views import * diff --git a/utils/_email.py b/utils/_email.py index fc1e064..a37beeb 100644 --- a/utils/_email.py +++ b/utils/_email.py @@ -7,14 +7,18 @@ import aiosmtplib as smtp from email.message import EmailMessage gmail_cfg = {"addr": "smtp.gmail.com", "username": config.email, "password": config.email_password, "port": 465} +TOKEN_LENGTH = 16 async def send_verification_code(user: discord.User, student_number: str, **kwargs) -> str: """Sends a verification code, returning said verification code, to the student.""" - code = secrets.token_hex(16) + code = secrets.token_hex(TOKEN_LENGTH) text = ( - f"Hey {user} ({student_number})! The code to join the Unscrupulous Nonsense is '{code}' - use " - f"'/verify {code}' in the bot's DMs to continue \N{dancer}\n\n~nex" + f"Hey {user} ({student_number})! The code to join the Unscrupulous Nonsense is '{code}'.\n\n" + f"Go back to the #verify channel, and click 'I have a verification code!', and put {code} in the modal" + f" that pops up\n\n" + f"If you have any issues getting in, feel free to reply to this email, or DM eek#7574.\n" + f"~Nex" ) msg = EmailMessage() msg["From"] = "B593764@my.leedscitycollege.ac.uk" diff --git a/utils/views.py b/utils/views.py new file mode 100644 index 0000000..de9a0e6 --- /dev/null +++ b/utils/views.py @@ -0,0 +1,151 @@ +import secrets + +import discord +import re +import orm + +from utils import send_verification_code, get_or_none, Student, VerifyCode, console, TOKEN_LENGTH, BannedStudentID + + +class VerifyView(discord.ui.View): + def __init__(self, ctx: discord.ApplicationContext): + self.ctx = ctx + super().__init__(timeout=300, disable_on_timeout=True) + + @discord.ui.button( + label="I have a verification code!", + emoji="\U0001f4e7", + custom_id="have" + ) + async def have(self, _, interaction1: discord.Interaction): + class Modal(discord.ui.Modal): + def __init__(self): + super().__init__( + discord.ui.InputText( + custom_id="code", + label="Verification Code", + placeholder="e.g: " + secrets.token_hex(TOKEN_LENGTH), + min_length=TOKEN_LENGTH*2, + max_length=TOKEN_LENGTH*2, + ), + title="Enter the verification code in your inbox", + ) + + async def callback(self, interaction: discord.Interaction): + await interaction.response.defer() + code = self.children[0].value + if not code: # timed out + self.stop() + return + else: + try: + existing: VerifyCode = await VerifyCode.objects.get(code=code) + except orm.NoMatch: + self.stop() + return await interaction.followup.send( + "\N{cross mark} Invalid or unknown verification code. Try again!", ephemeral=True + ) + else: + ban = await get_or_none(BannedStudentID, student_id=existing.student_id) + if ban is not None: + self.stop() + return await interaction.user.ban( + reason=f"Attempted to verify with banned student ID {ban.student_id}" + f" (originally associated with account {ban.associated_account})" + ) + await Student.objects.create(id=existing.student_id, user_id=interaction.user.id) + await existing.delete() + role = discord.utils.find(lambda r: r.name.lower() == "verified", interaction.guild.roles) + if role and role < interaction.guild.me.top_role: + member = await interaction.guild.fetch_member(interaction.user.id) + await member.add_roles(role, reason="Verified") + console.log(f"[green]{interaction.user} verified ({interaction.user.id}/{existing.student_id})") + self.stop() + return await interaction.followup.send( + "\N{white heavy check mark} Verification complete!", + ephemeral=True + ) + + await interaction1.response.send_modal(Modal()) + self.disable_all_items() + await interaction1.edit_original_response(view=self) + await interaction1.delete_original_response(delay=1) + + @discord.ui.button( + label="Send me a verification code.", + emoji="\U0001f4e5" + ) + async def send(self, btn: discord.ui.Button, interaction1: discord.Interaction): + class Modal(discord.ui.Modal): + def __init__(self): + super().__init__( + discord.ui.InputText( + custom_id="student_id", + label="What is your student ID", + placeholder="B...", + min_length=7, + max_length=7, + ), + title="Enter your student ID number", + ) + + async def callback(self, interaction: discord.Interaction): + await interaction.response.defer() + st = self.children[0].value + if not st: # timed out + return + + if not re.match(r"^B\d{6}$", st): + return await interaction.response.send_message( + "\N{cross mark} Invalid student ID.", + delete_after=60 + ) + + ex = await get_or_none(Student, id=st) + if ex: + return await interaction.response.send_message( + "\N{cross mark} Student ID is already associated.", + delete_after=60 + ) + + _code = await send_verification_code(interaction.user, st) + console.log(f"Sending verification email to {interaction.user} ({interaction.user.id}/{st})...") + __code = await VerifyCode.objects.create(code=_code, bind=interaction.id, student_id=st) + console.log(f"[green]Sent verification email to {interaction.user} ({interaction.user.id}/{st}): " + f"{_code!r}") + await interaction.followup.send( + "\N{white heavy check mark} Verification email sent to your college email " + f"({st}@my.leedscitycollege.ac.uk)\n" + f"Once you get that email, run this command again, with the first option being the 16" + f" character code.\n\n" + f">>> If you don't know how to access your email, go to , then " + f"sign in as `{st}@leedscitycollege.ac.uk` (notice there's no `my.` " + f"prefix to sign into gmail), and you should be greeted by your inbox. The default password " + f"is your birthday, !, and the first three letters of your first or last name" + f" (for example, `John Doe`, born on the 1st of february 2006, would be either " + f"`01022006!Joh` or `01022006!Doe`).", + ephemeral=True, + ) + + await interaction1.response.send_modal(Modal()) + btn.disabled = True + await interaction1.edit_original_response(view=self) + + @discord.ui.button( + label="Why do I need a verification code?", + emoji="\U0001f616" + ) + async def why(self, _, interaction: discord.Interaction): + await interaction.response.defer( + ephemeral=True + ) + await interaction.followup.send( + "In order to access this server, you need to enter your student ID.\n" + "We require this to make sure only **students** in our course can access the server.\n" + "Your B number (student ID) is found on your ID card (the one you use to scan into the building).\n" + "This is not invading your privacy, your B number is publicly visible, as it is the start of your email," + " plus can be found on google chat.", + ephemeral=True, + delete_after=60 + ) +