98 lines
3.4 KiB
C#
98 lines
3.4 KiB
C#
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Text;
|
|
using System.Security.Claims;
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Cryptography;
|
|
using Microsoft.AspNetCore.Identity;
|
|
|
|
using agologumApi.Models;
|
|
|
|
public class JwtService {
|
|
|
|
private readonly IConfiguration config_;
|
|
private readonly AppDbContext db_;
|
|
private readonly UserManager<User> userManager_;
|
|
|
|
public JwtService(IConfiguration config, AppDbContext db, UserManager<User> userManager) { // why the heck does c# not have initializer lists ?
|
|
config_ = config;
|
|
db_ = db;
|
|
userManager_ = userManager;
|
|
}
|
|
|
|
// create a jwt string given a user (user contains permissions which go into claims)
|
|
public async Task<string?> GenerateJwt(User user) {
|
|
|
|
// security stuff
|
|
string? jwtKey = config_["Jwt:Key"];
|
|
if(jwtKey == null) return null;
|
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey));
|
|
|
|
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
|
// make sure the user is real
|
|
if(user.UserName == null) return null;
|
|
|
|
// not too sure
|
|
var claims = new List<Claim> {
|
|
new Claim(ClaimTypes.Name, user.UserName),
|
|
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())
|
|
};
|
|
|
|
// add each permission that the user has into the claims
|
|
List<string>? permissions = user.Permissions;
|
|
if(permissions != null) {
|
|
foreach(string perm in permissions) {
|
|
claims.Add(new Claim("permission", perm));
|
|
}
|
|
}
|
|
|
|
// construct that token
|
|
var token = new JwtSecurityToken(
|
|
issuer: "agologum",
|
|
audience: "agologum",
|
|
claims: claims,
|
|
expires: DateTime.UtcNow.AddHours(2),
|
|
signingCredentials: creds
|
|
);
|
|
|
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
|
|
|
}
|
|
|
|
// generating a refresh token is just like a long random password
|
|
public string GenerateRefreshToken() {
|
|
|
|
byte[] randomBytes = new byte[64];
|
|
RandomNumberGenerator.Fill(randomBytes.AsSpan());
|
|
return Convert.ToBase64String(randomBytes);
|
|
|
|
}
|
|
|
|
// we store refresh tokens on our side to check against when a user requests a refresh
|
|
public async Task<RefreshToken?> GetRefreshToken(string refreshTokenString) {
|
|
return await db_.RefreshTokens.FirstOrDefaultAsync(u => u.Token == refreshTokenString);
|
|
}
|
|
|
|
// add a refresh token to the token db store
|
|
public async Task<RefreshToken> AddRefreshToken(RefreshToken refreshToken) {
|
|
db_.RefreshTokens.Add(refreshToken);
|
|
await db_.SaveChangesAsync();
|
|
return refreshToken;
|
|
}
|
|
|
|
// helper to get the User from the id that exists in a refresh token object
|
|
public async Task<User?> GetUser(string id) {
|
|
return await db_.Users.FindAsync(id);
|
|
} // since other places aren't good for having references to db contexts
|
|
|
|
// remove refresh token from our store; called when user logs out
|
|
public async Task<bool> RevokeRefreshToken(string refreshTokenString) {
|
|
var refreshToken = await db_.RefreshTokens.FirstOrDefaultAsync(u => u.Token == refreshTokenString);
|
|
if(refreshToken == null) return false;
|
|
refreshToken.IsRevoked = true;
|
|
await db_.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
} |