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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,6 @@
<PackageReference Include="Colorful.Console" Version="1.2.10" /> <PackageReference Include="Colorful.Console" Version="1.2.10" />
<PackageReference Include="CommandLineParser" Version="2.8.0" /> <PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="websocketsharp.core" Version="1.0.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="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.3.0" /> <PackageReference Include="McMaster.NETCore.Plugins" Version="1.3.0" />
<PackageReference Include="Nett" Version="0.15.0" /> <PackageReference Include="Nett" Version="0.15.0" />

View File

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

View File

@ -4,6 +4,11 @@ using MongoDB.Driver;
using Zirconium.Core; using Zirconium.Core;
using Zirconium.Core.Plugins.Interfaces; using Zirconium.Core.Plugins.Interfaces;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
using JWT.Algorithms;
using JWT.Builder;
using System;
using Zirconium.Utils;
namespace DefaultAuthProvider namespace DefaultAuthProvider
{ {
@ -17,7 +22,8 @@ namespace DefaultAuthProvider
public void Initialize(IPluginHostAPI hostModuleAPI) public void Initialize(IPluginHostAPI hostModuleAPI)
{ {
var db = hostModuleAPI.GetRawDatabase(); 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 IMongoDatabase _db;
private IMongoCollection<User> usersCol; private IMongoCollection<User> usersCol;
private string jwtSecret;
public DefaultAuthProvider(IMongoDatabase db) public DefaultAuthProvider(IMongoDatabase db, string jwtSecret)
{ {
this._db = db; this._db = db;
this.jwtSecret = jwtSecret;
this.usersCol = db.GetCollection<User>("default_auth_data"); this.usersCol = db.GetCollection<User>("default_auth_data");
_createUsernameUniqueIndex(); _createUsernameUniqueIndex();
} }
@ -71,7 +79,8 @@ namespace DefaultAuthProvider
{ {
var filter = Builders<User>.Filter.Eq("Username", username); var filter = Builders<User>.Filter.Eq("Username", username);
var user = usersCol.Find(filter).FirstOrDefault(); var user = usersCol.Find(filter).FirstOrDefault();
if (user == null) { if (user == null)
{
return false; return false;
} }
var valid = PasswordHasher.VerifyHash(pass, user.Salt, user.Password); var valid = PasswordHasher.VerifyHash(pass, user.Salt, user.Password);
@ -79,9 +88,28 @@ namespace DefaultAuthProvider
return valid; 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();
} }
} }
} }