153 lines
No EOL
5.4 KiB
C#
153 lines
No EOL
5.4 KiB
C#
using System.Diagnostics.CodeAnalysis;
|
|
using Iceshrimp.Backend.Core.Database;
|
|
using Iceshrimp.Backend.Core.Database.Tables;
|
|
using Iceshrimp.Backend.Core.Helpers;
|
|
using Iceshrimp.Backend.Core.Middleware;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Iceshrimp.Backend.Pages.OAuth;
|
|
|
|
[HideRequestDuration]
|
|
public class AuthorizeModel(DatabaseContext db) : PageModel
|
|
{
|
|
public OauthApp App = null!;
|
|
|
|
public List<string> Scopes = [];
|
|
public OauthToken? Token;
|
|
public List<User> AuthenticatedUsers = [];
|
|
[FromQuery(Name = "response_type")] public string ResponseType { get; set; } = null!;
|
|
[FromQuery(Name = "client_id")] public string ClientId { get; set; } = null!;
|
|
[FromQuery(Name = "redirect_uri")] public string RedirectUri { get; set; } = null!;
|
|
[FromQuery(Name = "force_login")] public bool ForceLogin { get; set; } = false;
|
|
[FromQuery(Name = "lang")] public string? Language { get; set; }
|
|
|
|
[FromQuery(Name = "scope")]
|
|
public string Scope
|
|
{
|
|
get => string.Join(' ', Scopes);
|
|
[MemberNotNull(nameof(Scopes))] set => Scopes = value.Split(' ').ToList();
|
|
}
|
|
|
|
public LoginData? TwoFactorFormData;
|
|
|
|
public async Task OnGet()
|
|
{
|
|
if (ResponseType == null || ClientId == null || RedirectUri == null)
|
|
throw GracefulException.BadRequest("Required parameters are missing or invalid");
|
|
App = await db.OauthApps.FirstOrDefaultAsync(p => p.ClientId == ClientId.Replace(' ', '+')) ??
|
|
throw GracefulException.BadRequest("Invalid client_id");
|
|
if (MastodonOauthHelpers.ExpandScopes(Scopes).Except(MastodonOauthHelpers.ExpandScopes(App.Scopes)).Any())
|
|
throw GracefulException.BadRequest("Cannot request more scopes than app");
|
|
if (ResponseType != "code")
|
|
throw GracefulException.BadRequest("Invalid response_type");
|
|
if (!App.RedirectUris.Contains(RedirectUri))
|
|
throw GracefulException.BadRequest("Cannot request redirect_uri not sent during app registration");
|
|
if (Request.Cookies.TryGetValue("sessions", out var sessions))
|
|
{
|
|
var tokens = sessions.Split(',');
|
|
AuthenticatedUsers = await db.Sessions
|
|
.Where(p => tokens.Contains(p.Token) && p.Active)
|
|
.Select(p => p.User)
|
|
.ToListAsync();
|
|
}
|
|
}
|
|
|
|
public async Task OnPost(
|
|
[FromForm] string? username, [FromForm] string? password, [FromForm] string? totp, [FromForm] string? userId,
|
|
[FromForm] bool supportsHtmlFormatting, [FromForm] bool autoDetectQuotes, [FromForm] bool isPleroma,
|
|
[FromForm] bool supportsInlineMedia
|
|
)
|
|
{
|
|
// Validate query parameters & populate model first
|
|
await OnGet();
|
|
|
|
var user = AuthenticatedUsers.FirstOrDefault(p => p.Id == userId);
|
|
|
|
if (user == null)
|
|
{
|
|
Exception Forbidden() => GracefulException.Forbidden("Invalid username or password");
|
|
if (username == null || password == null)
|
|
throw Forbidden();
|
|
|
|
user = await db.Users.FirstOrDefaultAsync(p => p.IsLocalUser &&
|
|
p.UsernameLower == username.ToLowerInvariant()) ??
|
|
throw Forbidden();
|
|
if (user.IsSystemUser) throw GracefulException.BadRequest("Cannot log in as system user");
|
|
var userSettings = await db.UserSettings.FirstOrDefaultAsync(p => p.User == user);
|
|
if (userSettings?.Password == null)
|
|
throw Forbidden();
|
|
if (AuthHelpers.ComparePassword(password, userSettings.Password) == false)
|
|
throw Forbidden();
|
|
|
|
if (userSettings.TwoFactorEnabled)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(totp))
|
|
{
|
|
if (userSettings.TwoFactorSecret == null)
|
|
throw new Exception("2FA is enabled but secret is null");
|
|
if (!TotpHelper.Validate(userSettings.TwoFactorSecret, totp))
|
|
{
|
|
SetTwoFactorFormData();
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetTwoFactorFormData();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
var token = new OauthToken
|
|
{
|
|
Id = IdHelpers.GenerateSnowflakeId(),
|
|
Active = false,
|
|
Code = CryptographyHelpers.GenerateRandomString(32),
|
|
Token = CryptographyHelpers.GenerateRandomString(32),
|
|
App = App,
|
|
User = user,
|
|
CreatedAt = DateTime.UtcNow,
|
|
Scopes = Scopes,
|
|
RedirectUri = RedirectUri,
|
|
AutoDetectQuotes = autoDetectQuotes,
|
|
SupportsHtmlFormatting = supportsHtmlFormatting,
|
|
IsPleroma = isPleroma,
|
|
SupportsInlineMedia = supportsInlineMedia,
|
|
};
|
|
|
|
await db.AddAsync(token);
|
|
await db.SaveChangesAsync();
|
|
|
|
Token = token;
|
|
return;
|
|
|
|
void SetTwoFactorFormData()
|
|
{
|
|
TwoFactorFormData = new LoginData
|
|
{
|
|
Username = username,
|
|
Password = password,
|
|
AutoDetectQuotes = autoDetectQuotes,
|
|
SupportsHtmlFormatting = supportsHtmlFormatting,
|
|
IsPleroma = isPleroma,
|
|
SupportsInlineMedia = supportsInlineMedia,
|
|
};
|
|
|
|
Response.Headers.CacheControl = "private, no-store, no-cache";
|
|
Response.Headers.Pragma = "no-cache";
|
|
}
|
|
}
|
|
|
|
public class LoginData
|
|
{
|
|
public required string Username;
|
|
public required string Password;
|
|
public required bool SupportsHtmlFormatting;
|
|
public required bool AutoDetectQuotes;
|
|
public required bool IsPleroma;
|
|
public required bool SupportsInlineMedia;
|
|
}
|
|
} |