mirror of
https://github.com/jackmerrill/hampbot.git
synced 2024-12-04 13:02:59 -08:00
add verify command
This commit is contained in:
parent
a95b599bd3
commit
e77b8b9f55
|
@ -41,6 +41,7 @@ WORKDIR /app
|
|||
|
||||
### Copy built binary application from 'builder' image
|
||||
COPY --from=builder /app/main .
|
||||
COPY --from=builder /app/verify.html .
|
||||
|
||||
### Copy the certs from builder
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
|
|
1
go.mod
1
go.mod
|
@ -21,6 +21,7 @@ require (
|
|||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/jackmerrill/go-mapbox v0.4.5 // indirect
|
||||
github.com/jackmerrill/hamp-api v0.0.0-20230818235104-8d222c9674c9 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -62,6 +62,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
|
|
270
internal/commands/util/verify.go
Normal file
270
internal/commands/util/verify.go
Normal file
|
@ -0,0 +1,270 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/smtp"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackmerrill/hampbot/internal/utils/config"
|
||||
"github.com/jackmerrill/hampbot/internal/utils/embed"
|
||||
"github.com/zekroTJA/shireikan"
|
||||
)
|
||||
|
||||
type VerifyCommand struct {
|
||||
}
|
||||
|
||||
func (c *VerifyCommand) GetInvokes() []string {
|
||||
return []string{"verify"}
|
||||
}
|
||||
|
||||
func (c *VerifyCommand) GetDescription() string {
|
||||
return "Verify your student/staff/faculty status with your school email."
|
||||
}
|
||||
|
||||
func (c *VerifyCommand) GetHelp() string {
|
||||
return "`verify` - `verify [email]`"
|
||||
}
|
||||
|
||||
func (c *VerifyCommand) GetGroup() string {
|
||||
return config.GroupUtil
|
||||
}
|
||||
|
||||
func (c *VerifyCommand) GetDomainName() string {
|
||||
return "hamp.util.verify"
|
||||
}
|
||||
|
||||
func (c *VerifyCommand) GetSubPermissionRules() []shireikan.SubPermission {
|
||||
return nil
|
||||
}
|
||||
func (c *VerifyCommand) IsExecutableInDMChannels() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type Verification struct {
|
||||
User *discordgo.User
|
||||
Email string
|
||||
Expires time.Time
|
||||
IsStaffFaculty bool
|
||||
}
|
||||
|
||||
var VerificationCodes map[string]Verification = make(map[string]Verification)
|
||||
|
||||
func (c *VerifyCommand) Exec(ctx shireikan.Context) error {
|
||||
// first check if the user is already verified (has the role)
|
||||
guildMember, err := ctx.GetSession().GuildMember(config.BotGuild, ctx.GetUser().ID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, role := range guildMember.Roles {
|
||||
if role == config.VerifiedRoleId {
|
||||
ctx.GetSession().ChannelMessageSendComplex(ctx.GetChannel().ID, &discordgo.MessageSend{
|
||||
Reference: ctx.GetMessage().Reference(),
|
||||
Embed: embed.NewErrorEmbed(ctx).SetTitle("Already Verified").SetDescription("You are already verified.").MessageEmbed,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// first arg is the email
|
||||
email := ctx.GetArgs().Get(0).AsString()
|
||||
|
||||
// check if the email is valid, and if it is a hampshire.edu email
|
||||
if !strings.Contains(email, "@hampshire.edu") {
|
||||
ctx.GetSession().ChannelMessageSendComplex(ctx.GetChannel().ID, &discordgo.MessageSend{
|
||||
Reference: ctx.GetMessage().Reference(),
|
||||
Embed: embed.NewErrorEmbed(ctx).SetTitle("Invalid Email").SetDescription("Please use a valid @hampshire.edu email.").MessageEmbed,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
code := uuid.New().String()
|
||||
|
||||
err = SendEmail([]string{email}, code, ctx.GetUser())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// expires in 5 minutes
|
||||
expires := time.Now().Add(time.Minute * 5)
|
||||
|
||||
m, err := ctx.GetSession().ChannelMessageSendComplex(ctx.GetChannel().ID, &discordgo.MessageSend{
|
||||
Reference: ctx.GetMessage().Reference(),
|
||||
Embed: embed.NewSuccessEmbed(ctx).SetTitle("Sent Verification Email").SetDescription("Waiting for you to verify...").AddField("Expires", fmt.Sprintf("<t:%d:R>", expires.Unix()), false).MessageEmbed,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if the email is student or staff/faculty
|
||||
// student emails are in the format of their initials plus their first year (e.g. jmm18). To be safe, lets allow up to six initials plus the year (e.g. jmmmmmm18)
|
||||
// staff/faculty emails are in the format of their first initial plus their last name (e.g. jsmith), or their first and last initial plus their department (e.g. jsIA)
|
||||
|
||||
hampnetUser := strings.Split(email, "@")[0]
|
||||
|
||||
// use regex to check
|
||||
// student
|
||||
if match, _ := regexp.MatchString(`^[a-z]{1,6}[0-9]{2}$`, hampnetUser); match {
|
||||
VerificationCodes[code] = Verification{
|
||||
User: ctx.GetUser(),
|
||||
Email: email,
|
||||
Expires: expires,
|
||||
IsStaffFaculty: false,
|
||||
}
|
||||
} else if match, _ := regexp.MatchString(`^[a-z]{1,2}[a-z]{1,2}[a-z]{1,2}[a-z]{1,2}$`, hampnetUser); match {
|
||||
VerificationCodes[code] = Verification{
|
||||
User: ctx.GetUser(),
|
||||
Email: email,
|
||||
Expires: expires,
|
||||
IsStaffFaculty: true,
|
||||
}
|
||||
} else {
|
||||
// potentially student, but not in the format of a student email
|
||||
VerificationCodes[code] = Verification{
|
||||
User: ctx.GetUser(),
|
||||
Email: email,
|
||||
Expires: expires,
|
||||
IsStaffFaculty: false,
|
||||
}
|
||||
}
|
||||
|
||||
// wait for the user to verify
|
||||
for {
|
||||
if time.Now().After(expires) {
|
||||
ctx.GetSession().ChannelMessageEditComplex(&discordgo.MessageEdit{
|
||||
ID: m.ID,
|
||||
Channel: ctx.GetChannel().ID,
|
||||
Embed: embed.NewErrorEmbed(ctx).SetTitle("Verification Expired").SetDescription("Please try again.").MessageEmbed,
|
||||
})
|
||||
}
|
||||
|
||||
_, ok := VerificationCodes[code]
|
||||
|
||||
if !ok {
|
||||
ctx.GetSession().ChannelMessageEditComplex(&discordgo.MessageEdit{
|
||||
ID: m.ID,
|
||||
Channel: ctx.GetChannel().ID,
|
||||
Embed: embed.NewSuccessEmbed(ctx).SetTitle("Verified!").SetDescription("You can access the server now.").MessageEmbed,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartWebserver(session *discordgo.Session) {
|
||||
// listen to /verify
|
||||
|
||||
http.HandleFunc("/verify", func(w http.ResponseWriter, r *http.Request) {
|
||||
code := r.URL.Query().Get("code")
|
||||
|
||||
if code == "" {
|
||||
http.Error(w, "Code is missing", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
v, ok := VerificationCodes[code]
|
||||
|
||||
if !ok {
|
||||
http.Error(w, "Code is invalid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := session.GuildMemberRoleAdd(config.BotGuild, v.User.ID, config.VerifiedRoleId)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to add role", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// remove the code from the map
|
||||
delete(VerificationCodes, code)
|
||||
|
||||
fmt.Fprintf(w, "Verified! You can close this tab now.")
|
||||
})
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "OK!")
|
||||
})
|
||||
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
|
||||
func SendEmail(to []string, code string, discordUser *discordgo.User) error {
|
||||
sender := "automated@jackmerrill.com"
|
||||
|
||||
user := os.Getenv("SMTP_USER")
|
||||
password := os.Getenv("SMTP_PASSWORD")
|
||||
|
||||
subject := "Hampshire Hangout Verification"
|
||||
tmpl, err := template.ParseFiles("verify.html")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
err = tmpl.Execute(buf, struct {
|
||||
Code string
|
||||
DiscordUserName string
|
||||
}{
|
||||
Code: code,
|
||||
DiscordUserName: discordUser.Username,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
request := Mail{
|
||||
Sender: sender,
|
||||
To: to,
|
||||
Subject: subject,
|
||||
Body: buf.String(),
|
||||
}
|
||||
|
||||
addr := "smtp.gmail.com:587"
|
||||
host := "smtp.gmail.com"
|
||||
|
||||
msg := BuildMessage(request)
|
||||
auth := smtp.PlainAuth("", user, password, host)
|
||||
err = smtp.SendMail(addr, auth, sender, to, []byte(msg))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func BuildMessage(mail Mail) string {
|
||||
msg := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\r\n"
|
||||
msg += fmt.Sprintf("From: %s\r\n", mail.Sender)
|
||||
msg += fmt.Sprintf("To: %s\r\n", strings.Join(mail.To, ";"))
|
||||
msg += fmt.Sprintf("Subject: %s\r\n", mail.Subject)
|
||||
msg += fmt.Sprintf("\r\n%s\r\n", mail.Body)
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
type Mail struct {
|
||||
Sender string
|
||||
To []string
|
||||
Subject string
|
||||
Body string
|
||||
}
|
|
@ -27,6 +27,9 @@ var (
|
|||
HampAPIHost = "api.hamp.sh"
|
||||
// HampAPIHost = "localhost:1323"
|
||||
// HampAPI = "http://localhost:1323"
|
||||
|
||||
VerifiedRoleId = "1020858770659745844"
|
||||
StaffFacultyRoleId = "1183920480361648228"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
4
main.go
4
main.go
|
@ -102,6 +102,10 @@ func main() {
|
|||
handler.Register(&fun.MetricTime{})
|
||||
log.Debug("Registered metrictime command")
|
||||
|
||||
handler.Register(&util.VerifyCommand{})
|
||||
go util.StartWebserver(session)
|
||||
log.Debug("Registered verify command")
|
||||
|
||||
log.Info("Registered all commands")
|
||||
|
||||
log.Info("Setting up activities...")
|
||||
|
|
50
verify.html
Normal file
50
verify.html
Normal file
|
@ -0,0 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Hampshire Hangout Email Verification</title>
|
||||
<link
|
||||
href="https://unpkg.com/tailwindcss@^3/dist/tailwind.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div
|
||||
class="flex items-center justify-center min-h-screen p-5 bg-gray-900 min-w-screen"
|
||||
>
|
||||
<div
|
||||
class="max-w-xl p-8 text-center text-gray-800 bg-white shadow-xl lg:max-w-3xl rounded-3xl lg:p-12"
|
||||
>
|
||||
<h3 class="text-2xl font-bold">Hampshire Hangout Email Verification</h3>
|
||||
<p class="mt-4 text-sm">
|
||||
Thanks for joining Hampshire Hangout, {{ .DiscordUserName }}! We're
|
||||
excited to have you join our community. Before you can start using
|
||||
your account, you need to verify your email address by clicking the
|
||||
button below:
|
||||
</p>
|
||||
<div class="mt-4">
|
||||
<a
|
||||
class="px-2 py-2 text-white bg-blue-600 rounded-md font-semibold"
|
||||
href="https://hampbot.fly.dev/verify?code={{ .Code }}"
|
||||
>Click to Verify Email</a
|
||||
>
|
||||
<p class="mt-4 text-sm">
|
||||
If you're having trouble clicking the "Verify Email Address" button,
|
||||
copy and paste the URL below into your web browser:
|
||||
<a
|
||||
href="https://hampbot.fly.dev/verify?code={{ .Code }}"
|
||||
class="text-blue-600"
|
||||
>https://hampbot.fly.dev/verify?code={{ .Code }}</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-sm mt-4 text-gray-600">
|
||||
If you didn't request this, ignore it!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user