Add Discord unlinking

This commit is contained in:
Ben Visness 2021-08-16 00:07:17 -05:00
parent d92bf9a9b8
commit 4c84bd2860
5 changed files with 88 additions and 4 deletions

View File

@ -273,7 +273,7 @@ func GetCurrentUserAsOAuth(ctx context.Context, accessToken string) (*User, erro
} }
func AddGuildMemberRole(ctx context.Context, userID, roleID string) error { func AddGuildMemberRole(ctx context.Context, userID, roleID string) error {
const name = "Delete Message" const name = "Add Guild Member Role"
path := fmt.Sprintf("/guilds/%s/members/%s/roles/%s", config.Config.Discord.GuildID, userID, roleID) path := fmt.Sprintf("/guilds/%s/members/%s/roles/%s", config.Config.Discord.GuildID, userID, roleID)
res, err := doWithRateLimiting(ctx, name, func(ctx context.Context) *http.Request { res, err := doWithRateLimiting(ctx, name, func(ctx context.Context) *http.Request {
@ -292,6 +292,27 @@ func AddGuildMemberRole(ctx context.Context, userID, roleID string) error {
return nil return nil
} }
func RemoveGuildMemberRole(ctx context.Context, userID, roleID string) error {
const name = "Remove Guild Member Role"
path := fmt.Sprintf("/guilds/%s/members/%s/roles/%s", config.Config.Discord.GuildID, userID, roleID)
logging.ExtractLogger(ctx).Warn().Str("path", path).Msg("I dunno")
res, err := doWithRateLimiting(ctx, name, func(ctx context.Context) *http.Request {
return makeRequest(ctx, http.MethodDelete, path, nil)
})
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusNoContent {
logErrorResponse(ctx, name, res, "")
return oops.New(nil, "got unexpected status code when removing role")
}
return nil
}
func logErrorResponse(ctx context.Context, name string, res *http.Response, msg string) { func logErrorResponse(ctx context.Context, name string, res *http.Response, msg string) {
dump, err := httputil.DumpResponse(res, true) dump, err := httputil.DumpResponse(res, true)
if err != nil { if err != nil {

View File

@ -561,6 +561,12 @@ func BuildDiscordOAuthCallback() string {
return Url("/_discord_callback", nil) return Url("/_discord_callback", nil)
} }
var RegexDiscordUnlink = regexp.MustCompile("^/_discord_unlink$")
func BuildDiscordUnlink() string {
return Url("/_discord_unlink", nil)
}
/* /*
* Assets * Assets
*/ */

View File

@ -5,8 +5,13 @@ Wow a discord
<div> <div>
{{ with .DiscordUser }} {{ with .DiscordUser }}
<div>
<img src="{{ .Avatar }}"> <img src="{{ .Avatar }}">
{{ .Username }}#{{ .Discriminator }} {{ .Username }}#{{ .Discriminator }}
</div>
<form action="{{ $.UnlinkURL }}" method="POST">
<button>Unlink Account</button>
</form>
{{ else }} {{ else }}
<a href="{{ $.AuthorizeURL }}">Link your account</a> <a href="{{ $.AuthorizeURL }}">Link your account</a>
{{ end }} {{ end }}

View File

@ -41,6 +41,7 @@ func DiscordTest(c *RequestContext) ResponseData {
templates.BaseData templates.BaseData
DiscordUser *templates.DiscordUser DiscordUser *templates.DiscordUser
AuthorizeURL string AuthorizeURL string
UnlinkURL string
} }
baseData := getBaseData(c) baseData := getBaseData(c)
@ -56,6 +57,7 @@ func DiscordTest(c *RequestContext) ResponseData {
td := templateData{ td := templateData{
BaseData: baseData, BaseData: baseData,
AuthorizeURL: fmt.Sprintf("https://discord.com/api/oauth2/authorize?%s", params.Encode()), AuthorizeURL: fmt.Sprintf("https://discord.com/api/oauth2/authorize?%s", params.Encode()),
UnlinkURL: hmnurl.BuildDiscordUnlink(),
} }
if userDiscord != nil { if userDiscord != nil {
@ -111,12 +113,13 @@ func DiscordOAuthCallback(c *RequestContext) ResponseData {
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch Discord user info")) return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch Discord user info"))
} }
// TODO: Add the role on Discord // Add the role on Discord
err = discord.AddGuildMemberRole(c.Context(), user.ID, config.Config.Discord.MemberRoleID) err = discord.AddGuildMemberRole(c.Context(), user.ID, config.Config.Discord.MemberRoleID)
if err != nil { if err != nil {
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to add member role")) return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to add member role"))
} }
// Add the user to our database
_, err = c.Conn.Exec(c.Context(), _, err = c.Conn.Exec(c.Context(),
` `
INSERT INTO handmade_discorduser (username, discriminator, access_token, refresh_token, avatar, locale, userid, expiry, hmn_user_id) INSERT INTO handmade_discorduser (username, discriminator, access_token, refresh_token, avatar, locale, userid, expiry, hmn_user_id)
@ -138,3 +141,51 @@ func DiscordOAuthCallback(c *RequestContext) ResponseData {
return c.Redirect(hmnurl.BuildDiscordTest(), http.StatusSeeOther) return c.Redirect(hmnurl.BuildDiscordTest(), http.StatusSeeOther)
} }
func DiscordUnlink(c *RequestContext) ResponseData {
tx, err := c.Conn.Begin(c.Context())
if err != nil {
panic(err)
}
defer tx.Rollback(c.Context())
iDiscordUser, err := db.QueryOne(c.Context(), tx, models.DiscordUser{},
`
SELECT $columns
FROM handmade_discorduser
WHERE hmn_user_id = $1
`,
c.CurrentUser.ID,
)
if err != nil {
if errors.Is(err, db.ErrNoMatchingRows) {
return c.Redirect(hmnurl.BuildDiscordTest(), http.StatusSeeOther)
} else {
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to get Discord user for unlink"))
}
}
discordUser := iDiscordUser.(*models.DiscordUser)
_, err = tx.Exec(c.Context(),
`
DELETE FROM handmade_discorduser
WHERE id = $1
`,
discordUser.ID,
)
if err != nil {
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to delete Discord user"))
}
err = tx.Commit(c.Context())
if err != nil {
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to commit Discord user delete"))
}
err = discord.RemoveGuildMemberRole(c.Context(), discordUser.UserID, config.Config.Discord.MemberRoleID)
if err != nil {
c.Logger.Warn().Err(err).Msg("failed to remove member role on unlink")
}
return c.Redirect(hmnurl.BuildDiscordTest(), http.StatusSeeOther)
}

View File

@ -199,6 +199,7 @@ func NewWebsiteRoutes(conn *pgxpool.Pool, perfCollector *perf.PerfCollector) htt
mainRoutes.GET(hmnurl.RegexDiscordTest, authMiddleware(DiscordTest)) // TODO: Delete this route mainRoutes.GET(hmnurl.RegexDiscordTest, authMiddleware(DiscordTest)) // TODO: Delete this route
mainRoutes.GET(hmnurl.RegexDiscordOAuthCallback, authMiddleware(DiscordOAuthCallback)) mainRoutes.GET(hmnurl.RegexDiscordOAuthCallback, authMiddleware(DiscordOAuthCallback))
mainRoutes.POST(hmnurl.RegexDiscordUnlink, authMiddleware(DiscordUnlink))
mainRoutes.GET(hmnurl.RegexProjectCSS, ProjectCSS) mainRoutes.GET(hmnurl.RegexProjectCSS, ProjectCSS)
mainRoutes.GET(hmnurl.RegexEditorPreviewsJS, func(c *RequestContext) ResponseData { mainRoutes.GET(hmnurl.RegexEditorPreviewsJS, func(c *RequestContext) ResponseData {