-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDiscordAuthHandler.cs
More file actions
159 lines (126 loc) · 5.17 KB
/
DiscordAuthHandler.cs
File metadata and controls
159 lines (126 loc) · 5.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using QuickFinder.Options;
namespace QuickFinder;
public class DiscordAuthHandler(
UserService userService,
SignInManager<User> signInManager,
IOptions<DiscordAuthOptions> options,
ILogger<DiscordAuthHandler> logger,
IHttpClientFactory httpClientFactory
)
{
private readonly DiscordAuthOptions options = options.Value;
private readonly string[] ScopeList = ["identify", "email", "guilds.join"];
private const string API_ENDPOINT = "https://discord.com/api/v10";
private readonly string CLIENT_ID = options.Value.ClientId;
private readonly string CLIENT_SECRET = options.Value.ClientSecret;
public bool IsEnabled => options.IsValid;
public string CreateAuthUrl(string host)
{
var clientId = options.ClientId;
var scope = string.Join("+", ScopeList);
var redirect_uri = System.Net.WebUtility.UrlEncode(CreateRedirectUri(host));
var discord_url =
$"https://discord.com/oauth2/authorize?client_id={clientId}&response_type=code&redirect_uri={redirect_uri}&scope={scope}";
return discord_url;
}
private string CreateRedirectUri(string host)
{
return $"{options.RedirectScheme}://{host}/LoginDiscord";
}
public async Task Authenticate(string code, string host)
{
using HttpClient client = httpClientFactory.CreateClient();
var tokenResponse = await ExchangeCodeAsync(code, host);
var token = tokenResponse.AccessToken;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var identity_response = await client.GetAsync($"{API_ENDPOINT}/users/@me");
identity_response.EnsureSuccessStatusCode();
var identityJSON = await identity_response.Content.ReadAsStringAsync();
var identity = JsonSerializer.Deserialize<DiscordUserIdentity>(identityJSON);
if (identity?.Id == null)
{
logger.LogError("Discord identity response missing id.");
throw new Exception("Invalid Discord identity response.");
}
if (identity?.Email == null)
{
logger.LogError("Discord identity response missing email (is email scope missing?).");
throw new Exception("Invalid Discord identity response.");
}
var user = await userService.GetUserByDiscordId(identity.Email, identity.Id);
user ??= await userService.CreateDiscordUser(
identity.Email,
identity.Username,
identity.Id,
identity.GlobalName
);
await signInManager.SignInAsync(user, true);
await userService.SetDiscordToken(user, token);
}
public async Task<DiscordTokenResponse> ExchangeCodeAsync(string code, string host)
{
using HttpClient client = httpClientFactory.CreateClient();
var data = new FormUrlEncodedContent(
new[]
{
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("redirect_uri", CreateRedirectUri(host)),
}
);
var authValue = Convert.ToBase64String(
Encoding.UTF8.GetBytes($"{CLIENT_ID}:{CLIENT_SECRET}")
);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
authValue
);
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded")
);
var response = await client.PostAsync($"{API_ENDPOINT}/oauth2/token", data);
response.EnsureSuccessStatusCode();
var responseJSON = await response.Content.ReadAsStringAsync();
var tokenResponse = JsonSerializer.Deserialize<DiscordTokenResponse>(responseJSON);
if (tokenResponse == null)
{
throw new Exception("TokenResponse could not be deserialized");
}
return tokenResponse;
}
}
// Models for Discord API responses
public class DiscordTokenResponse
{
[JsonPropertyName("access_token")]
public required string AccessToken { get; set; }
[JsonPropertyName("token_type")]
public required string TokenType { get; set; }
[JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }
[JsonPropertyName("refresh_token")]
public required string RefreshToken { get; set; }
[JsonPropertyName("scope")]
public required string Scope { get; set; }
}
public class DiscordUserIdentity
{
[JsonPropertyName("id")]
public required string Id { get; set; }
[JsonPropertyName("username")]
public required string Username { get; set; }
[JsonPropertyName("global_name")]
public required string GlobalName { get; set; }
[JsonPropertyName("avatar")]
public required string Avatar { get; set; }
[JsonPropertyName("email")]
public required string Email { get; set; }
[JsonPropertyName("verified")]
public bool Verified { get; set; }
}