refactor(backend): use explicit types instead of var by default

This commit is contained in:
sam 2024-12-08 15:07:25 +01:00
parent bc7fd6d804
commit 649988db25
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
52 changed files with 506 additions and 420 deletions

View file

@ -33,14 +33,16 @@ public class AuthController(
config.GoogleAuth.Enabled,
config.TumblrAuth.Enabled
);
var state = HttpUtility.UrlEncode(await keyCacheService.GenerateAuthStateAsync(ct));
string state = HttpUtility.UrlEncode(await keyCacheService.GenerateAuthStateAsync(ct));
string? discord = null;
if (config.DiscordAuth is { ClientId: not null, ClientSecret: not null })
{
discord =
$"https://discord.com/oauth2/authorize?response_type=code"
+ $"&client_id={config.DiscordAuth.ClientId}&scope=identify"
+ $"&prompt=none&state={state}"
+ $"&redirect_uri={HttpUtility.UrlEncode($"{config.BaseUrl}/auth/callback/discord")}";
}
return Ok(new UrlsResponse(config.EmailAuth.Enabled, discord, null, null));
}
@ -86,7 +88,7 @@ public class AuthController(
)]
public async Task<IActionResult> GetAuthMethodAsync(Snowflake id)
{
var authMethod = await db
AuthMethod? authMethod = await db
.AuthMethods.Include(a => a.FediverseApplication)
.FirstOrDefaultAsync(a => a.UserId == CurrentUser!.Id && a.Id == id);
if (authMethod == null)
@ -99,17 +101,19 @@ public class AuthController(
[Authorize("*")]
public async Task<IActionResult> DeleteAuthMethodAsync(Snowflake id)
{
var authMethods = await db
List<AuthMethod> authMethods = await db
.AuthMethods.Where(a => a.UserId == CurrentUser!.Id)
.ToListAsync();
if (authMethods.Count < 2)
{
throw new ApiError(
"You cannot remove your last authentication method.",
HttpStatusCode.BadRequest,
ErrorCode.LastAuthMethod
);
}
var authMethod = authMethods.FirstOrDefault(a => a.Id == id);
AuthMethod? authMethod = authMethods.FirstOrDefault(a => a.Id == id);
if (authMethod == null)
throw new ApiError.NotFound("No authentication method with that ID found.");

View file

@ -34,8 +34,10 @@ public class DiscordAuthController(
CheckRequirements();
await keyCacheService.ValidateAuthStateAsync(req.State);
var remoteUser = await remoteAuthService.RequestDiscordTokenAsync(req.Code);
var user = await authService.AuthenticateUserAsync(AuthType.Discord, remoteUser.Id);
RemoteAuthService.RemoteUser remoteUser = await remoteAuthService.RequestDiscordTokenAsync(
req.Code
);
User? user = await authService.AuthenticateUserAsync(AuthType.Discord, remoteUser.Id);
if (user != null)
return Ok(await authService.GenerateUserTokenAsync(user));
@ -45,23 +47,14 @@ public class DiscordAuthController(
remoteUser.Id
);
var ticket = AuthUtils.RandomToken();
string ticket = AuthUtils.RandomToken();
await keyCacheService.SetKeyAsync(
$"discord:{ticket}",
remoteUser,
Duration.FromMinutes(20)
);
return Ok(
new CallbackResponse(
HasAccount: false,
Ticket: ticket,
RemoteUsername: remoteUser.Username,
User: null,
Token: null,
ExpiresAt: null
)
);
return Ok(new CallbackResponse(false, ticket, remoteUser.Username, null, null, null));
}
[HttpPost("register")]
@ -70,9 +63,10 @@ public class DiscordAuthController(
[FromBody] AuthController.OauthRegisterRequest req
)
{
var remoteUser = await keyCacheService.GetKeyAsync<RemoteAuthService.RemoteUser>(
$"discord:{req.Ticket}"
);
RemoteAuthService.RemoteUser? remoteUser =
await keyCacheService.GetKeyAsync<RemoteAuthService.RemoteUser>(
$"discord:{req.Ticket}"
);
if (remoteUser == null)
throw new ApiError.BadRequest("Invalid ticket", "ticket", req.Ticket);
if (
@ -88,7 +82,7 @@ public class DiscordAuthController(
throw new ApiError.BadRequest("Invalid ticket", "ticket", req.Ticket);
}
var user = await authService.CreateUserWithRemoteAuthAsync(
User user = await authService.CreateUserWithRemoteAuthAsync(
req.Username,
AuthType.Discord,
remoteUser.Id,
@ -104,12 +98,12 @@ public class DiscordAuthController(
{
CheckRequirements();
var state = await remoteAuthService.ValidateAddAccountRequestAsync(
string state = await remoteAuthService.ValidateAddAccountRequestAsync(
CurrentUser!.Id,
AuthType.Discord
);
var url =
string url =
$"https://discord.com/oauth2/authorize?response_type=code"
+ $"&client_id={config.DiscordAuth.ClientId}&scope=identify"
+ $"&prompt=none&state={state}"
@ -132,10 +126,12 @@ public class DiscordAuthController(
AuthType.Discord
);
var remoteUser = await remoteAuthService.RequestDiscordTokenAsync(req.Code);
RemoteAuthService.RemoteUser remoteUser = await remoteAuthService.RequestDiscordTokenAsync(
req.Code
);
try
{
var authMethod = await authService.AddAuthMethodAsync(
AuthMethod authMethod = await authService.AddAuthMethodAsync(
CurrentUser.Id,
AuthType.Discord,
remoteUser.Id,
@ -169,8 +165,10 @@ public class DiscordAuthController(
private void CheckRequirements()
{
if (!config.DiscordAuth.Enabled)
{
throw new ApiError.BadRequest(
"Discord authentication is not enabled on this instance."
);
}
}
}

View file

@ -39,11 +39,7 @@ public class EmailAuthController(
if (!req.Email.Contains('@'))
throw new ApiError.BadRequest("Email is invalid", "email", req.Email);
var state = await keyCacheService.GenerateRegisterEmailStateAsync(
req.Email,
userId: null,
ct
);
string state = await keyCacheService.GenerateRegisterEmailStateAsync(req.Email, null, ct);
// If there's already a user with that email address, pretend we sent an email but actually ignore it
if (
@ -63,23 +59,14 @@ public class EmailAuthController(
{
CheckRequirements();
var state = await keyCacheService.GetRegisterEmailStateAsync(req.State);
RegisterEmailState? state = await keyCacheService.GetRegisterEmailStateAsync(req.State);
if (state is not { ExistingUserId: null })
throw new ApiError.BadRequest("Invalid state", "state", req.State);
var ticket = AuthUtils.RandomToken();
string ticket = AuthUtils.RandomToken();
await keyCacheService.SetKeyAsync($"email:{ticket}", state.Email, Duration.FromMinutes(20));
return Ok(
new CallbackResponse(
HasAccount: false,
Ticket: ticket,
RemoteUsername: state.Email,
User: null,
Token: null,
ExpiresAt: null
)
);
return Ok(new CallbackResponse(false, ticket, state.Email, null, null, null));
}
[HttpPost("register")]
@ -89,14 +76,18 @@ public class EmailAuthController(
{
CheckRequirements();
var email = await keyCacheService.GetKeyAsync($"email:{req.Ticket}");
string? email = await keyCacheService.GetKeyAsync($"email:{req.Ticket}");
if (email == null)
throw new ApiError.BadRequest("Unknown ticket", "ticket", req.Ticket);
var user = await authService.CreateUserWithPasswordAsync(req.Username, email, req.Password);
var frontendApp = await db.GetFrontendApplicationAsync();
User user = await authService.CreateUserWithPasswordAsync(
req.Username,
email,
req.Password
);
Application frontendApp = await db.GetFrontendApplicationAsync();
var (tokenStr, token) = authService.GenerateToken(
(string? tokenStr, Token? token) = authService.GenerateToken(
user,
frontendApp,
["*"],
@ -110,7 +101,7 @@ public class EmailAuthController(
return Ok(
new AuthController.AuthResponse(
await userRenderer.RenderUserAsync(user, selfUser: user, renderMembers: false),
await userRenderer.RenderUserAsync(user, user, renderMembers: false),
tokenStr,
token.ExpiresAt
)
@ -126,19 +117,16 @@ public class EmailAuthController(
{
CheckRequirements();
var (user, authenticationResult) = await authService.AuthenticateUserAsync(
req.Email,
req.Password,
ct
);
(User? user, AuthService.EmailAuthenticationResult authenticationResult) =
await authService.AuthenticateUserAsync(req.Email, req.Password, ct);
if (authenticationResult == AuthService.EmailAuthenticationResult.MfaRequired)
throw new NotImplementedException("MFA is not implemented yet");
var frontendApp = await db.GetFrontendApplicationAsync(ct);
Application frontendApp = await db.GetFrontendApplicationAsync(ct);
_logger.Debug("Logging user {Id} in with email and password", user.Id);
var (tokenStr, token) = authService.GenerateToken(
(string? tokenStr, Token? token) = authService.GenerateToken(
user,
frontendApp,
["*"],
@ -152,12 +140,7 @@ public class EmailAuthController(
return Ok(
new AuthController.AuthResponse(
await userRenderer.RenderUserAsync(
user,
selfUser: user,
renderMembers: false,
ct: ct
),
await userRenderer.RenderUserAsync(user, user, renderMembers: false, ct: ct),
tokenStr,
token.ExpiresAt
)
@ -184,7 +167,7 @@ public class EmailAuthController(
{
CheckRequirements();
var emails = await db
List<AuthMethod> emails = await db
.AuthMethods.Where(m => m.UserId == CurrentUser!.Id && m.AuthType == AuthType.Email)
.ToListAsync();
if (emails.Count > AuthUtils.MaxAuthMethodsPerType)
@ -207,12 +190,12 @@ public class EmailAuthController(
await db.SaveChangesAsync();
}
var state = await keyCacheService.GenerateRegisterEmailStateAsync(
string state = await keyCacheService.GenerateRegisterEmailStateAsync(
req.Email,
userId: CurrentUser!.Id
CurrentUser!.Id
);
var emailExists = await db
bool emailExists = await db
.AuthMethods.Where(m => m.AuthType == AuthType.Email && m.RemoteId == req.Email)
.AnyAsync();
if (emailExists)
@ -230,13 +213,13 @@ public class EmailAuthController(
{
CheckRequirements();
var state = await keyCacheService.GetRegisterEmailStateAsync(req.State);
RegisterEmailState? state = await keyCacheService.GetRegisterEmailStateAsync(req.State);
if (state?.ExistingUserId != CurrentUser!.Id)
throw new ApiError.BadRequest("Invalid state", "state", req.State);
try
{
var authMethod = await authService.AddAuthMethodAsync(
AuthMethod authMethod = await authService.AddAuthMethodAsync(
CurrentUser.Id,
AuthType.Email,
state.Email
@ -252,7 +235,7 @@ public class EmailAuthController(
authMethod.Id,
AuthType.Email,
authMethod.RemoteId,
RemoteUsername: null
null
)
);
}

View file

@ -34,7 +34,7 @@ public class FediverseAuthController(
if (instance.Any(c => c is '@' or ':' or '/') || !instance.Contains('.'))
throw new ApiError.BadRequest("Not a valid domain.", "instance", instance);
var url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh);
string url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh);
return Ok(new AuthController.SingleUrlResponse(url));
}
@ -42,22 +42,19 @@ public class FediverseAuthController(
[ProducesResponseType<CallbackResponse>(statusCode: StatusCodes.Status200OK)]
public async Task<IActionResult> FediverseCallbackAsync([FromBody] CallbackRequest req)
{
var app = await fediverseAuthService.GetApplicationAsync(req.Instance);
var remoteUser = await fediverseAuthService.GetRemoteFediverseUserAsync(
app,
req.Code,
req.State
);
FediverseApplication app = await fediverseAuthService.GetApplicationAsync(req.Instance);
FediverseAuthService.FediverseUser remoteUser =
await fediverseAuthService.GetRemoteFediverseUserAsync(app, req.Code, req.State);
var user = await authService.AuthenticateUserAsync(
User? user = await authService.AuthenticateUserAsync(
AuthType.Fediverse,
remoteUser.Id,
instance: app
app
);
if (user != null)
return Ok(await authService.GenerateUserTokenAsync(user));
var ticket = AuthUtils.RandomToken();
string ticket = AuthUtils.RandomToken();
await keyCacheService.SetKeyAsync(
$"fediverse:{ticket}",
new FediverseTicketData(app.Id, remoteUser),
@ -66,12 +63,12 @@ public class FediverseAuthController(
return Ok(
new CallbackResponse(
HasAccount: false,
Ticket: ticket,
RemoteUsername: $"@{remoteUser.Username}@{app.Domain}",
User: null,
Token: null,
ExpiresAt: null
false,
ticket,
$"@{remoteUser.Username}@{app.Domain}",
null,
null,
null
)
);
}
@ -82,14 +79,16 @@ public class FediverseAuthController(
[FromBody] AuthController.OauthRegisterRequest req
)
{
var ticketData = await keyCacheService.GetKeyAsync<FediverseTicketData>(
FediverseTicketData? ticketData = await keyCacheService.GetKeyAsync<FediverseTicketData>(
$"fediverse:{req.Ticket}",
delete: true
true
);
if (ticketData == null)
throw new ApiError.BadRequest("Invalid ticket", "ticket", req.Ticket);
var app = await db.FediverseApplications.FindAsync(ticketData.ApplicationId);
FediverseApplication? app = await db.FediverseApplications.FindAsync(
ticketData.ApplicationId
);
if (app == null)
throw new FoxnounsError("Null application found for ticket");
@ -111,12 +110,12 @@ public class FediverseAuthController(
throw new ApiError.BadRequest("Invalid ticket", "ticket", req.Ticket);
}
var user = await authService.CreateUserWithRemoteAuthAsync(
User user = await authService.CreateUserWithRemoteAuthAsync(
req.Username,
AuthType.Fediverse,
ticketData.User.Id,
ticketData.User.Username,
instance: app
app
);
return Ok(await authService.GenerateUserTokenAsync(user));
@ -132,13 +131,13 @@ public class FediverseAuthController(
if (instance.Any(c => c is '@' or ':' or '/') || !instance.Contains('.'))
throw new ApiError.BadRequest("Not a valid domain.", "instance", instance);
var state = await remoteAuthService.ValidateAddAccountRequestAsync(
string state = await remoteAuthService.ValidateAddAccountRequestAsync(
CurrentUser!.Id,
AuthType.Fediverse,
instance
);
var url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh, state);
string url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh, state);
return Ok(new AuthController.SingleUrlResponse(url));
}
@ -153,11 +152,12 @@ public class FediverseAuthController(
req.Instance
);
var app = await fediverseAuthService.GetApplicationAsync(req.Instance);
var remoteUser = await fediverseAuthService.GetRemoteFediverseUserAsync(app, req.Code);
FediverseApplication app = await fediverseAuthService.GetApplicationAsync(req.Instance);
FediverseAuthService.FediverseUser remoteUser =
await fediverseAuthService.GetRemoteFediverseUserAsync(app, req.Code);
try
{
var authMethod = await authService.AddAuthMethodAsync(
AuthMethod authMethod = await authService.AddAuthMethodAsync(
CurrentUser.Id,
AuthType.Fediverse,
remoteUser.Id,