Refactor auth system: make token creation/validation fully pluggable

This commit is contained in:
ChronosX88 2020-10-10 22:22:18 +04:00
parent eb849a8388
commit dea57a3cb2
Signed by: ChronosXYZ
GPG Key ID: 085A69A82C8C511A
9 changed files with 58 additions and 51 deletions

View File

@ -1,9 +1,5 @@
using System.Collections.Generic;
using System;
using JWT.Algorithms;
using JWT.Builder;
using Newtonsoft.Json;
using Zirconium.Utils;
using Zirconium.Core.Plugins.Interfaces;
using System.Linq;
@ -27,15 +23,8 @@ namespace Zirconium.Core
public string CreateToken(string entityID, string deviceID, long tokenExpirationMillis)
{
JWTPayload payload = new JWTPayload();
payload.DeviceID = deviceID;
payload.EntityID = entityID;
return new JwtBuilder()
.WithAlgorithm(new HMACSHA256Algorithm()) // symmetric
.WithSecret(_secretString)
.AddClaim("exp", DateTimeOffset.UtcNow.AddMilliseconds(tokenExpirationMillis).ToUnixTimeSeconds())
.AddClaims(payload.ToDictionary())
.Encode();
if (DefaultAuthProvider == null) throw new Exception("Default auth provider isn't specified");
return DefaultAuthProvider.CreateAuthToken(entityID, deviceID, tokenExpirationMillis);
}
public string CreateToken(string entityID, string deviceID)
@ -43,22 +32,10 @@ namespace Zirconium.Core
return CreateToken(entityID, deviceID, DEFAULT_TOKEN_EXPIRATION_TIME_HOURS);
}
public JWTPayload ValidateToken(string token)
public SessionAuthData ValidateToken(string token)
{
var jsonPayload = new JwtBuilder()
.WithAlgorithm(new HMACSHA256Algorithm()) // symmetric
.WithSecret(_secretString)
.MustVerifySignature()
.Decode(token);
var payload = JsonConvert.DeserializeObject<JWTPayload>(jsonPayload);
if (DefaultAuthProvider == null)
{
throw new Exception("Default auth provider isn't specified");
}
var validToken = DefaultAuthProvider.TestToken(token, payload);
if (!validToken)
return null;
return payload;
if (DefaultAuthProvider == null) throw new Exception("Default auth provider isn't specified");
return DefaultAuthProvider.TestToken(token);
}
public void AddAuthProvider(IAuthProvider provider)

View File

@ -1,3 +1,5 @@
using System;
using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;
@ -15,7 +17,7 @@ namespace Zirconium.Core.Models
public string From { get; set; }
[JsonProperty("to", NullValueHandling = NullValueHandling.Ignore)]
public string To { get; set; }
public string[] To { get; set; }
[JsonProperty("ok")]
public bool Ok { get; set; }
@ -26,28 +28,31 @@ namespace Zirconium.Core.Models
[JsonProperty("payload")]
public IDictionary<string, object> Payload { get; set; }
public BaseMessage() { }
public BaseMessage(BaseMessage message, bool reply)
{
public BaseMessage() {
Payload = new Dictionary<string, object>();
ID = Guid.NewGuid().ToString();
}
public BaseMessage(BaseMessage message, bool reply) : this()
{
if (message != null)
{
ID = message.ID;
MessageType = message.MessageType;
if (reply)
{
From = message.To;
To = message.From;
// TODO probably need to fix it
From = message.To.First();
To = new string[] { message.From };
}
else
{
From = message.From;
To = message.To;
AuthToken = message.AuthToken;
}
Ok = message.Ok;
AuthToken = message.AuthToken;
Payload = message.Payload;
}
}

View File

@ -5,7 +5,7 @@ namespace Zirconium.Core.Models
public class Session
{
public string LastTokenHash { get; set; }
public JWTPayload LastTokenPayload { get; set; }
public SessionAuthData LastTokenPayload { get; set; }
public IPAddress ClientAddress { get; set; }
public ConnectionHandler ConnectionHandler { get; set; }
}

View File

@ -3,13 +3,15 @@ namespace Zirconium.Core.Plugins.Interfaces
public interface IAuthProvider
{
// Method for checking validity of access token in each message
bool TestToken(string token, JWTPayload payload);
SessionAuthData TestToken(string token);
// Method for testing password when logging in
bool TestPassword(string username, string pass);
// User registration logic
void CreateUser(string username, string pass);
string CreateAuthToken(string entityID, string deviceID, long tokenExpirationMillis);
string GetAuthProviderName();
}
}

View File

@ -51,7 +51,7 @@ namespace Zirconium.Core
}
if (session.LastTokenHash != hash)
{
JWTPayload tokenPayload;
SessionAuthData tokenPayload;
try
{
tokenPayload = _app.AuthManager.ValidateToken(message.AuthToken);

View File

@ -1,13 +1,8 @@
using Newtonsoft.Json;
namespace Zirconium.Core
{
public class JWTPayload
public class SessionAuthData
{
[JsonProperty("entityID")]
public string EntityID { get; set; }
[JsonProperty("deviceID")]
public string DeviceID { get; set; }
}
}

View File

@ -7,7 +7,6 @@
<PackageReference Include="Colorful.Console" Version="1.2.10" />
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="websocketsharp.core" Version="1.0.0" />
<PackageReference Include="JWT" Version="7.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.3.0" />
<PackageReference Include="Nett" Version="0.15.0" />

View File

@ -10,5 +10,6 @@
</ProjectReference>
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.2.1" />
<PackageReference Include="JWT" Version="7.2.1" />
</ItemGroup>
</Project>

View File

@ -4,6 +4,11 @@ using MongoDB.Driver;
using Zirconium.Core;
using Zirconium.Core.Plugins.Interfaces;
using System.Linq;
using Newtonsoft.Json;
using JWT.Algorithms;
using JWT.Builder;
using System;
using Zirconium.Utils;
namespace DefaultAuthProvider
{
@ -17,7 +22,8 @@ namespace DefaultAuthProvider
public void Initialize(IPluginHostAPI hostModuleAPI)
{
var db = hostModuleAPI.GetRawDatabase();
hostModuleAPI.ProvideAuth(new DefaultAuthProvider(db));
var jwtSecret = hostModuleAPI.GetSettings(this)["JWTSecret"];
hostModuleAPI.ProvideAuth(new DefaultAuthProvider(db, jwtSecret));
}
}
@ -34,10 +40,12 @@ namespace DefaultAuthProvider
private IMongoDatabase _db;
private IMongoCollection<User> usersCol;
private string jwtSecret;
public DefaultAuthProvider(IMongoDatabase db)
public DefaultAuthProvider(IMongoDatabase db, string jwtSecret)
{
this._db = db;
this.jwtSecret = jwtSecret;
this.usersCol = db.GetCollection<User>("default_auth_data");
_createUsernameUniqueIndex();
}
@ -71,7 +79,8 @@ namespace DefaultAuthProvider
{
var filter = Builders<User>.Filter.Eq("Username", username);
var user = usersCol.Find(filter).FirstOrDefault();
if (user == null) {
if (user == null)
{
return false;
}
var valid = PasswordHasher.VerifyHash(pass, user.Salt, user.Password);
@ -79,9 +88,28 @@ namespace DefaultAuthProvider
return valid;
}
public bool TestToken(string token, JWTPayload payload)
public SessionAuthData TestToken(string token)
{
return true; // TODO add enchanced token validation later
var jsonPayload = new JwtBuilder()
.WithAlgorithm(new HMACSHA256Algorithm()) // symmetric
.WithSecret(this.jwtSecret)
.MustVerifySignature()
.Decode(token);
var payload = JsonConvert.DeserializeObject<SessionAuthData>(jsonPayload);
return payload; // TODO add enchanced token validation
}
public string CreateAuthToken(string entityID, string deviceID, long tokenExpirationMillis)
{
SessionAuthData payload = new SessionAuthData();
payload.DeviceID = deviceID;
payload.EntityID = entityID;
return new JwtBuilder()
.WithAlgorithm(new HMACSHA256Algorithm()) // symmetric
.WithSecret(this.jwtSecret)
.AddClaim("exp", DateTimeOffset.UtcNow.AddMilliseconds(tokenExpirationMillis).ToUnixTimeSeconds())
.AddClaims(payload.ToDictionary())
.Encode();
}
}
}