[backend/razor] Allow logging in as users that the frontend has already authenticated (ISH-406)

This reduces friction when logging in to multiple apps.
This commit is contained in:
Laura Hausmann 2024-07-16 20:34:10 +02:00
parent 1ace285d35
commit e5e66e8a3d
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
3 changed files with 59 additions and 21 deletions

View file

@ -29,11 +29,28 @@
<input type="checkbox" name="autoDetectQuotes" id="autoDetectQuotes" value="1"/> <input type="checkbox" name="autoDetectQuotes" id="autoDetectQuotes" value="1"/>
<label for="autoDetectQuotes">Automatically detect quotes</label> <label for="autoDetectQuotes">Automatically detect quotes</label>
</div> </div>
<div class="margin-bottom-5px margin-top-10px"> @if (Model.AuthenticatedUsers.Count > 0)
Log in below to confirm this: {
</div> <div class="margin-top-5px">
<input type="text" placeholder="Username" name="username"/> @foreach (var user in Model.AuthenticatedUsers)
<input type="password" placeholder="Password" name="password"/> {
<button type="submit" name="userId" value="@user.Id">Log in as @@@user.Username</button>
}
</div>
<div class="margin-bottom-5px margin-top-5px">
Alternatively, sign in with to a different account below:
</div>
<input type="text" placeholder="Username" name="username"/>
<input type="password" placeholder="Password" name="password"/>
}
else
{
<div class="margin-bottom-5px margin-top-10px">
Log in below to confirm this:
</div>
<input type="text" placeholder="Username" name="username" required/>
<input type="password" placeholder="Password" name="password" required/>
}
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
</div> </div>

View file

@ -16,11 +16,12 @@ public class AuthorizeModel(DatabaseContext db) : PageModel
public List<string> Scopes = []; public List<string> Scopes = [];
public OauthToken? Token; public OauthToken? Token;
[FromQuery(Name = "response_type")] public string ResponseType { get; set; } = null!; public List<User> AuthenticatedUsers = [];
[FromQuery(Name = "client_id")] public string ClientId { get; set; } = null!; [FromQuery(Name = "response_type")] public string ResponseType { get; set; } = null!;
[FromQuery(Name = "redirect_uri")] public string RedirectUri { get; set; } = null!; [FromQuery(Name = "client_id")] public string ClientId { get; set; } = null!;
[FromQuery(Name = "force_login")] public bool ForceLogin { get; set; } = false; [FromQuery(Name = "redirect_uri")] public string RedirectUri { get; set; } = null!;
[FromQuery(Name = "lang")] public string? Language { get; set; } [FromQuery(Name = "force_login")] public bool ForceLogin { get; set; } = false;
[FromQuery(Name = "lang")] public string? Language { get; set; }
[FromQuery(Name = "scope")] [FromQuery(Name = "scope")]
public string Scope public string Scope
@ -41,25 +42,41 @@ public class AuthorizeModel(DatabaseContext db) : PageModel
throw GracefulException.BadRequest("Invalid response_type"); throw GracefulException.BadRequest("Invalid response_type");
if (!App.RedirectUris.Contains(RedirectUri)) if (!App.RedirectUris.Contains(RedirectUri))
throw GracefulException.BadRequest("Cannot request redirect_uri not sent during app registration"); 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( public async Task OnPost(
[FromForm] string username, [FromForm] string password, [FromForm] bool supportsHtmlFormatting, [FromForm] string? username, [FromForm] string? password, [FromForm] string? userId,
[FromForm] bool autoDetectQuotes [FromForm] bool supportsHtmlFormatting, [FromForm] bool autoDetectQuotes
) )
{ {
// Validate query parameters first // Validate query parameters & populate model first
await OnGet(); await OnGet();
var user = await db.Users.FirstOrDefaultAsync(p => p.IsLocalUser && var user = AuthenticatedUsers.FirstOrDefault(p => p.Id == userId);
p.UsernameLower == username.ToLowerInvariant());
if (user == null) if (user == null)
throw GracefulException.Forbidden("Invalid username or password"); {
var userSettings = await db.UserSettings.FirstOrDefaultAsync(p => p.User == user); Exception Forbidden() => GracefulException.Forbidden("Invalid username or password");
if (userSettings?.Password == null) if (username == null || password == null)
throw GracefulException.Forbidden("Invalid username or password"); throw Forbidden();
if (AuthHelpers.ComparePassword(password, userSettings.Password) == false)
throw GracefulException.Forbidden("Invalid username or password"); user = await db.Users.FirstOrDefaultAsync(p => p.IsLocalUser &&
p.UsernameLower == username.ToLowerInvariant()) ??
throw Forbidden();
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();
}
var token = new OauthToken var token = new OauthToken
{ {

View file

@ -6,6 +6,10 @@
margin-bottom: 5px; margin-bottom: 5px;
} }
.margin-top-5px {
margin-top: 5px;
}
.margin-top-10px { .margin-top-10px {
margin-top: 10px; margin-top: 10px;
} }