1using Dreamine.Database.Abstractions;
25 "SELECT Id, Provider, ProviderKey, Email, DisplayName, AvatarUrl, PasswordHash, " +
26 "TermsAcceptedAtUtc, PrivacyPolicyAcceptedAtUtc, AgeConfirmedAtUtc, CreatedAt, LastLoginAt " +
27 "FROM Users WHERE Provider = @Provider AND ProviderKey = @ProviderKey LIMIT 1";
75 _database = database ??
throw new ArgumentNullException(nameof(database));
166 CancellationToken cancellationToken =
default)
168 ArgumentException.ThrowIfNullOrWhiteSpace(provider);
169 ArgumentException.ThrowIfNullOrWhiteSpace(providerKey);
171 var now = DateTime.UtcNow;
174 new { Provider = provider, ProviderKey = providerKey },
175 cancellationToken).ConfigureAwait(
false)).FirstOrDefault();
177 if (existing is not
null)
179 existing.Email = email ??
string.Empty;
180 existing.DisplayName = displayName ??
string.Empty;
181 existing.AvatarUrl = avatarUrl ??
string.Empty;
182 existing.LastLoginAt = now;
183 await
_database.UpdateAsync(existing, cancellationToken).ConfigureAwait(
false);
190 ProviderKey = providerKey,
191 Email = email ??
string.Empty,
192 DisplayName = displayName ??
string.Empty,
193 AvatarUrl = avatarUrl ??
string.Empty,
194 PasswordHash =
string.Empty,
199 await
_database.InsertAsync(created, cancellationToken).ConfigureAwait(
false);
203 new { Provider = provider, ProviderKey = providerKey },
204 cancellationToken).ConfigureAwait(
false)).First();
239 public async Task<AuthUser?>
GetByIdAsync(
long id, CancellationToken cancellationToken =
default)
242 "SELECT Id, Provider, ProviderKey, Email, DisplayName, AvatarUrl, PasswordHash, " +
243 "TermsAcceptedAtUtc, PrivacyPolicyAcceptedAtUtc, AgeConfirmedAtUtc, CreatedAt, LastLoginAt " +
244 "FROM Users WHERE Id = @Id LIMIT 1",
246 cancellationToken).ConfigureAwait(
false);
248 return rows.FirstOrDefault();
310 CancellationToken cancellationToken =
default)
312 var user = await
GetByIdAsync(
id, cancellationToken).ConfigureAwait(
false);
318 displayName = displayName.Trim();
319 if (
string.IsNullOrWhiteSpace(displayName))
321 throw new InvalidOperationException(
"표시 이름을 입력해 주세요.");
324 user.DisplayName = displayName;
325 await
_database.UpdateAsync(user, cancellationToken).ConfigureAwait(
false);
371 DateTime acceptedAtUtc,
372 CancellationToken cancellationToken =
default)
374 var user = await
GetByIdAsync(
id, cancellationToken).ConfigureAwait(
false);
380 user.TermsAcceptedAtUtc = acceptedAtUtc;
381 user.PrivacyPolicyAcceptedAtUtc = acceptedAtUtc;
382 user.AgeConfirmedAtUtc = acceptedAtUtc;
383 await
_database.UpdateAsync(user, cancellationToken).ConfigureAwait(
false);
461 string currentPassword,
463 CancellationToken cancellationToken =
default)
465 var user = await
GetByIdAsync(
id, cancellationToken).ConfigureAwait(
false);
471 if (!
string.Equals(user.Provider,
LocalProvider, StringComparison.OrdinalIgnoreCase))
473 throw new InvalidOperationException(
"소셜 로그인 계정의 비밀번호는 해당 로그인 제공자에서 관리됩니다.");
478 throw new InvalidOperationException(
"현재 비밀번호가 올바르지 않습니다.");
481 ArgumentException.ThrowIfNullOrWhiteSpace(newPassword);
482 if (newPassword.Length < 8)
484 throw new InvalidOperationException(
"새 비밀번호는 8자 이상이어야 합니다.");
488 await
_database.UpdateAsync(user, cancellationToken).ConfigureAwait(
false);
568 CancellationToken cancellationToken =
default)
571 ArgumentException.ThrowIfNullOrWhiteSpace(password);
573 if (password.Length < 8)
575 throw new InvalidOperationException(
"비밀번호는 8자 이상이어야 합니다.");
579 if (existing is not
null)
581 throw new InvalidOperationException(
"이미 가입된 이메일입니다.");
584 var now = DateTime.UtcNow;
590 DisplayName =
string.IsNullOrWhiteSpace(displayName) ? email : displayName.Trim(),
591 AvatarUrl =
string.Empty,
597 await
_database.InsertAsync(user, cancellationToken).ConfigureAwait(
false);
661 CancellationToken cancellationToken =
default)
676 user.LastLoginAt = DateTime.UtcNow;
679 user.PasswordHash = upgradedHash;
682 await
_database.UpdateAsync(user, cancellationToken).ConfigureAwait(
false);
720 CancellationToken cancellationToken)
723 "SELECT Id, Provider, ProviderKey, Email, DisplayName, AvatarUrl, PasswordHash, " +
724 "TermsAcceptedAtUtc, PrivacyPolicyAcceptedAtUtc, AgeConfirmedAtUtc, CreatedAt, LastLoginAt " +
725 "FROM Users WHERE Provider = @Provider AND ProviderKey = @ProviderKey LIMIT 1",
727 cancellationToken).ConfigureAwait(
false);
729 return rows.FirstOrDefault();
743 .Select(column => column.Name)
744 .ToHashSet(StringComparer.OrdinalIgnoreCase);
748 _database.ExecuteNonQuery(
"ALTER TABLE Users ADD COLUMN PasswordHash TEXT NOT NULL DEFAULT ''");
782 if (!columns.Contains(columnName))
784 _database.ExecuteNonQuery($
"ALTER TABLE Users ADD COLUMN {columnName} TEXT NULL");
830 ArgumentException.ThrowIfNullOrWhiteSpace(email);
831 return email.Trim().ToLowerInvariant();
852 public string Name {
get;
set; } =
string.Empty;
PasswordHashVerificationResult
static PasswordHashVerificationResult VerifyPassword(string password, string storedHash, out string? upgradedHash)
static string HashPassword(string password)
DateTime? PrivacyPolicyAcceptedAtUtc
DateTime? AgeConfirmedAtUtc
DateTime? TermsAcceptedAtUtc
async Task< AuthUser?> FindLocalByEmailAsync(string email, CancellationToken cancellationToken)
async Task< AuthUser > UpsertAsync(string provider, string providerKey, string email, string displayName, string avatarUrl, CancellationToken cancellationToken=default)
async Task< AuthUser > CreateLocalAsync(string email, string displayName, string password, CancellationToken cancellationToken=default)
async Task< AuthUser?> ChangeLocalPasswordAsync(long id, string currentPassword, string newPassword, CancellationToken cancellationToken=default)
SqliteUserStore(IDatabaseProvider database)
async Task< AuthUser?> AcceptRequiredConsentsAsync(long id, DateTime acceptedAtUtc, CancellationToken cancellationToken=default)
const string LocalProvider
async Task< AuthUser?> ValidateLocalAsync(string email, string password, CancellationToken cancellationToken=default)
async Task< AuthUser?> UpdateDisplayNameAsync(long id, string displayName, CancellationToken cancellationToken=default)
readonly IDatabaseProvider _database
void AddNullableDateColumnIfMissing(HashSet< string > columns, string columnName)
async Task< AuthUser?> GetByIdAsync(long id, CancellationToken cancellationToken=default)
static string NormalizeEmail(string email)