1- using System . IdentityModel . Tokens . Jwt ;
2- using System . Security . Claims ;
3- using System . Text ;
1+ using System . Security . Claims ;
2+ using Microsoft . AspNetCore . Authentication ;
43using Microsoft . AspNetCore . Authorization ;
4+ using Microsoft . AspNetCore . Http ;
55using Microsoft . AspNetCore . Mvc ;
6- using Microsoft . EntityFrameworkCore ;
7- using Microsoft . IdentityModel . Tokens ;
8- using MoonCore . Exceptions ;
9- using MoonCore . Extended . Abstractions ;
106using Moonlight . ApiServer . Configuration ;
11- using Moonlight . ApiServer . Database . Entities ;
7+ using Moonlight . ApiServer . Implementations . LocalAuth ;
128using Moonlight . ApiServer . Interfaces ;
13- using Moonlight . Shared . Http . Requests . Auth ;
149using Moonlight . Shared . Http . Responses . Auth ;
1510
1611namespace Moonlight . ApiServer . Http . Controllers . Auth ;
@@ -19,93 +14,116 @@ namespace Moonlight.ApiServer.Http.Controllers.Auth;
1914[ Route ( "api/auth" ) ]
2015public class AuthController : Controller
2116{
17+ private readonly IAuthenticationSchemeProvider SchemeProvider ;
18+ private readonly IEnumerable < IAuthCheckExtension > Extensions ;
2219 private readonly AppConfiguration Configuration ;
23- private readonly DatabaseRepository < User > UserRepository ;
24- private readonly IOAuth2Provider OAuth2Provider ;
2520
2621 public AuthController (
27- AppConfiguration configuration ,
28- DatabaseRepository < User > userRepository ,
29- IOAuth2Provider oAuth2Provider
22+ IAuthenticationSchemeProvider schemeProvider ,
23+ IEnumerable < IAuthCheckExtension > extensions ,
24+ AppConfiguration configuration
3025 )
3126 {
32- UserRepository = userRepository ;
33- OAuth2Provider = oAuth2Provider ;
27+ SchemeProvider = schemeProvider ;
28+ Extensions = extensions ;
3429 Configuration = configuration ;
3530 }
3631
37- [ AllowAnonymous ]
38- [ HttpGet ( "start" ) ]
39- public async Task < LoginStartResponse > Start ( )
32+ [ HttpGet ]
33+ public async Task < AuthSchemeResponse [ ] > GetSchemes ( )
4034 {
41- var url = await OAuth2Provider . Start ( ) ;
35+ var schemes = await SchemeProvider . GetAllSchemesAsync ( ) ;
4236
43- return new LoginStartResponse ( )
44- {
45- Url = url
46- } ;
37+ var allowedSchemes = Configuration . Authentication . EnabledSchemes ;
38+
39+ return schemes
40+ . Where ( x => allowedSchemes . Contains ( x . Name ) )
41+ . Select ( scheme => new AuthSchemeResponse ( )
42+ {
43+ DisplayName = scheme . DisplayName ?? scheme . Name ,
44+ Identifier = scheme . Name
45+ } )
46+ . ToArray ( ) ;
4747 }
4848
49- [ AllowAnonymous ]
50- [ HttpPost ( "complete" ) ]
51- public async Task < LoginCompleteResponse > Complete ( [ FromBody ] LoginCompleteRequest request )
49+ [ HttpGet ( "{identifier:alpha}" ) ]
50+ public async Task StartScheme ( [ FromRoute ] string identifier )
5251 {
53- var user = await OAuth2Provider . Complete ( request . Code ) ;
54-
55- if ( user == null )
56- throw new HttpApiException ( "Unable to load user data" , 500 ) ;
52+ // Validate identifier against our enable list
53+ var allowedSchemes = Configuration . Authentication . EnabledSchemes ;
5754
58- // Generate token
59- var securityTokenDescriptor = new SecurityTokenDescriptor ( )
55+ if ( ! allowedSchemes . Contains ( identifier ) )
6056 {
61- Expires = DateTime . Now . AddHours ( Configuration . Authentication . TokenDuration ) ,
62- IssuedAt = DateTime . Now ,
63- NotBefore = DateTime . Now . AddMinutes ( - 1 ) ,
64- Claims = new Dictionary < string , object > ( )
65- {
66- {
67- "userId" ,
68- user . Id
69- } ,
70- {
71- "permissions" ,
72- string . Join ( ";" , user . Permissions )
73- }
74- } ,
75- SigningCredentials = new SigningCredentials (
76- new SymmetricSecurityKey (
77- Encoding . UTF8 . GetBytes ( Configuration . Authentication . Secret )
78- ) ,
79- SecurityAlgorithms . HmacSha256
80- ) ,
81- Issuer = Configuration . PublicUrl ,
82- Audience = Configuration . PublicUrl
83- } ;
57+ await Results
58+ . Problem (
59+ "Invalid scheme identifier provided" ,
60+ statusCode : 404
61+ )
62+ . ExecuteAsync ( HttpContext ) ;
8463
85- var jwtSecurityTokenHandler = new JwtSecurityTokenHandler ( ) ;
86- var securityToken = jwtSecurityTokenHandler . CreateToken ( securityTokenDescriptor ) ;
64+ return ;
65+ }
8766
88- var jwt = jwtSecurityTokenHandler . WriteToken ( securityToken ) ;
67+ // Now we can check if it even exists
68+ var scheme = await SchemeProvider . GetSchemeAsync ( identifier ) ;
8969
90- return new ( )
70+ if ( scheme == null )
9171 {
92- AccessToken = jwt
93- } ;
72+ await Results
73+ . Problem (
74+ "Invalid scheme identifier provided" ,
75+ statusCode : 404
76+ )
77+ . ExecuteAsync ( HttpContext ) ;
78+
79+ return ;
80+ }
81+
82+ // Everything fine, challenge the frontend
83+ await HttpContext . ChallengeAsync (
84+ scheme . Name ,
85+ new AuthenticationProperties ( )
86+ {
87+ RedirectUri = "/"
88+ }
89+ ) ;
9490 }
9591
9692 [ Authorize ]
9793 [ HttpGet ( "check" ) ]
98- public async Task < CheckResponse > Check ( )
94+ public async Task < AuthClaimResponse [ ] > Check ( )
9995 {
100- var userIdStr = User . FindFirstValue ( "userId" ) ! ;
101- var userId = int . Parse ( userIdStr ) ;
102- var user = await UserRepository . Get ( ) . FirstAsync ( x => x . Id == userId ) ;
96+ var username = User . FindFirstValue ( ClaimTypes . Name ) ! ;
97+ var id = User . FindFirstValue ( ClaimTypes . NameIdentifier ) ! ;
98+ var email = User . FindFirstValue ( ClaimTypes . Email ) ! ;
99+ var userId = User . FindFirstValue ( "UserId" ) ! ;
100+ var permissions = User . FindFirstValue ( "Permissions" ) ! ;
103101
104- return new ( )
102+ // Create basic set of claims used by the frontend
103+ var claims = new List < AuthClaimResponse > ( )
105104 {
106- Email = user . Email ,
107- Username = user . Username ,
108- Permissions = user . Permissions
105+ new ( ClaimTypes . Name , username ) ,
106+ new ( ClaimTypes . NameIdentifier , id ) ,
107+ new ( ClaimTypes . Email , email ) ,
108+ new ( "UserId" , userId ) ,
109+ new ( "Permissions" , permissions )
109110 } ;
111+
112+ // Enrich the frontend claims by extensions (used by plugins)
113+ foreach ( var extension in Extensions )
114+ {
115+ claims . AddRange (
116+ await extension . GetFrontendClaims ( User )
117+ ) ;
118+ }
119+
120+ return claims . ToArray ( ) ;
121+ }
122+
123+ [ HttpGet ( "logout" ) ]
124+ public async Task Logout ( )
125+ {
126+ await HttpContext . SignOutAsync ( ) ;
127+ await Results . Redirect ( "/" ) . ExecuteAsync ( HttpContext ) ;
110128 }
111129}
0 commit comments