Convert to new namespace stuff and implicit usings

This commit is contained in:
AeonLucid
2022-01-07 06:07:05 +01:00
parent ce2ea7f295
commit 2f01cf7411
85 changed files with 2538 additions and 2696 deletions
@@ -1,9 +1,8 @@
namespace Prospect.Server.Api.Services.Auth
namespace Prospect.Server.Api.Services.Auth;
public static class AuthClaimTypes
{
public static class AuthClaimTypes
{
public const string UserId = "user_id";
public const string EntityId = "entity_id";
public const string Type = "type";
}
public const string UserId = "user_id";
public const string EntityId = "entity_id";
public const string Type = "type";
}
@@ -1,83 +1,80 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Prospect.Server.Api.Services.Database.Models;
namespace Prospect.Server.Api.Services.Auth
namespace Prospect.Server.Api.Services.Auth;
public class AuthTokenService
{
public class AuthTokenService
private const string DefaultIssuer = "ProspectApi";
private const string DefaultAudience = "Prospect";
private readonly SymmetricSecurityKey _securityKey;
private readonly JwtSecurityTokenHandler _tokenHandler;
public AuthTokenService(IOptions<AuthTokenSettings> options)
{
private const string DefaultIssuer = "ProspectApi";
private const string DefaultAudience = "Prospect";
private readonly SymmetricSecurityKey _securityKey;
private readonly JwtSecurityTokenHandler _tokenHandler;
public AuthTokenService(IOptions<AuthTokenSettings> options)
{
_securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(options.Value.Secret));
_tokenHandler = new JwtSecurityTokenHandler();
}
_securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(options.Value.Secret));
_tokenHandler = new JwtSecurityTokenHandler();
}
private string CreateToken(IEnumerable<Claim> claims)
private string CreateToken(IEnumerable<Claim> claims)
{
var tokenDescriptor = new SecurityTokenDescriptor
{
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddDays(7),
Issuer = DefaultIssuer,
Audience = DefaultAudience,
SigningCredentials = new SigningCredentials(_securityKey, SecurityAlgorithms.HmacSha256Signature)
};
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddDays(7),
Issuer = DefaultIssuer,
Audience = DefaultAudience,
SigningCredentials = new SigningCredentials(_securityKey, SecurityAlgorithms.HmacSha256Signature)
};
var token = _tokenHandler.CreateToken(tokenDescriptor);
return _tokenHandler.WriteToken(token);
}
var token = _tokenHandler.CreateToken(tokenDescriptor);
return _tokenHandler.WriteToken(token);
}
public string GenerateUser(PlayFabEntity entity)
public string GenerateUser(PlayFabEntity entity)
{
return CreateToken(new[]
{
return CreateToken(new[]
{
new Claim(AuthClaimTypes.UserId, entity.UserId),
new Claim(AuthClaimTypes.EntityId, entity.Id),
new Claim(AuthClaimTypes.Type, AuthType.User),
});
}
new Claim(AuthClaimTypes.UserId, entity.UserId),
new Claim(AuthClaimTypes.EntityId, entity.Id),
new Claim(AuthClaimTypes.Type, AuthType.User),
});
}
public string GenerateEntity(PlayFabEntity entity)
public string GenerateEntity(PlayFabEntity entity)
{
return CreateToken(new[]
{
return CreateToken(new[]
{
new Claim(AuthClaimTypes.UserId, entity.UserId),
new Claim(AuthClaimTypes.EntityId, entity.Id),
new Claim(AuthClaimTypes.Type, AuthType.Entity),
});
}
new Claim(AuthClaimTypes.UserId, entity.UserId),
new Claim(AuthClaimTypes.EntityId, entity.Id),
new Claim(AuthClaimTypes.Type, AuthType.Entity),
});
}
public ClaimsPrincipal Validate(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
public ClaimsPrincipal Validate(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
try
try
{
return tokenHandler.ValidateToken(token, new TokenValidationParameters
{
return tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidIssuer = DefaultIssuer,
ValidAudience = DefaultAudience,
IssuerSigningKey = _securityKey
}, out _);
}
catch
{
return null;
}
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidIssuer = DefaultIssuer,
ValidAudience = DefaultAudience,
IssuerSigningKey = _securityKey
}, out _);
}
catch
{
return null;
}
}
}
@@ -1,7 +1,6 @@
namespace Prospect.Server.Api.Services.Auth
namespace Prospect.Server.Api.Services.Auth;
public class AuthTokenSettings
{
public class AuthTokenSettings
{
public string Secret { get; set; }
}
public string Secret { get; set; }
}
@@ -1,8 +1,7 @@
namespace Prospect.Server.Api.Services.Auth
namespace Prospect.Server.Api.Services.Auth;
public static class AuthType
{
public static class AuthType
{
public const string User = "User";
public const string Entity = "Entity";
}
public const string User = "User";
public const string Entity = "Entity";
}
@@ -1,20 +1,18 @@
using System;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication;
using Prospect.Server.Api.Services.Auth.Entity;
using Prospect.Server.Api.Services.Auth.User;
namespace Prospect.Server.Api.Services.Auth
namespace Prospect.Server.Api.Services.Auth;
public static class AuthenticationBuilderExtensions
{
public static class AuthenticationBuilderExtensions
public static AuthenticationBuilder AddUserAuthentication(this AuthenticationBuilder authenticationBuilder, Action<UserAuthenticationOptions> options)
{
public static AuthenticationBuilder AddUserAuthentication(this AuthenticationBuilder authenticationBuilder, Action<UserAuthenticationOptions> options)
{
return authenticationBuilder.AddScheme<UserAuthenticationOptions, UserAuthenticationHandler>(UserAuthenticationOptions.DefaultScheme, options);
}
return authenticationBuilder.AddScheme<UserAuthenticationOptions, UserAuthenticationHandler>(UserAuthenticationOptions.DefaultScheme, options);
}
public static AuthenticationBuilder AddEntityAuthentication(this AuthenticationBuilder authenticationBuilder, Action<EntityAuthenticationOptions> options)
{
return authenticationBuilder.AddScheme<EntityAuthenticationOptions, EntityAuthenticationHandler>(EntityAuthenticationOptions.DefaultScheme, options);
}
public static AuthenticationBuilder AddEntityAuthentication(this AuthenticationBuilder authenticationBuilder, Action<EntityAuthenticationOptions> options)
{
return authenticationBuilder.AddScheme<EntityAuthenticationOptions, EntityAuthenticationHandler>(EntityAuthenticationOptions.DefaultScheme, options);
}
}
@@ -1,87 +1,81 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Prospect.Server.Api.Models.Client;
using Prospect.Server.Api.Services.Auth.Extensions;
namespace Prospect.Server.Api.Services.Auth.Entity
namespace Prospect.Server.Api.Services.Auth.Entity;
public class EntityAuthenticationHandler : AuthenticationHandler<EntityAuthenticationOptions>
{
public class EntityAuthenticationHandler : AuthenticationHandler<EntityAuthenticationOptions>
private const string Header = "X-EntityToken";
private const string Type = AuthType.Entity;
private readonly AuthTokenService _authTokenService;
public EntityAuthenticationHandler(
IOptionsMonitor<EntityAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
AuthTokenService authTokenService) : base(options, logger, encoder, clock)
{
private const string Header = "X-EntityToken";
private const string Type = AuthType.Entity;
_authTokenService = authTokenService;
}
private readonly AuthTokenService _authTokenService;
public EntityAuthenticationHandler(
IOptionsMonitor<EntityAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
AuthTokenService authTokenService) : base(options, logger, encoder, clock)
{
_authTokenService = authTokenService;
}
private ClientResponse Res { get; set; } = new ClientResponse
{
Code = 401,
Status = "Unauthorized",
Error = "NotAuthenticated",
ErrorCode = 1074,
ErrorMessage = "This API method does not allow anonymous callers."
};
private ClientResponse Res { get; set; } = new ClientResponse
{
Code = 401,
Status = "Unauthorized",
Error = "NotAuthenticated",
ErrorCode = 1074,
ErrorMessage = "This API method does not allow anonymous callers."
};
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.TryGetValue(Header, out var headerValues) || headerValues.Count == 0)
{
if (!Request.Headers.TryGetValue(Header, out var headerValues) || headerValues.Count == 0)
{
return Task.FromResult(AuthenticateResult.Fail("No header"));
}
return Task.FromResult(AuthenticateResult.Fail("No header"));
}
var headerValue = headerValues.FirstOrDefault();
var headerValue = headerValues.FirstOrDefault();
if (string.IsNullOrWhiteSpace(headerValue))
{
return Task.FromResult(AuthenticateResult.Fail("Empty header value"));
}
Res.Error = "EntityTokenInvalid";
Res.ErrorCode = 1335;
Res.ErrorMessage = "EntityTokenInvalid";
var token = _authTokenService.Validate(headerValue);
if (token != null)
{
var identity = new ClaimsIdentity(token.Claims, Options.AuthenticationType);
var identities = new List<ClaimsIdentity> { identity };
var principal = new ClaimsPrincipal(identities);
if (principal.FindAuthType() != Type)
{
return Task.FromResult(AuthenticateResult.Fail("Invalid auth type"));
}
var ticket = new AuthenticationTicket(principal, Options.Scheme);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
return Task.FromResult(AuthenticateResult.Fail("Invalid JWT"));
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
if (string.IsNullOrWhiteSpace(headerValue))
{
Response.StatusCode = 401;
Response.ContentType = "application/json";
await Response.WriteAsync(JsonSerializer.Serialize(Res));
return Task.FromResult(AuthenticateResult.Fail("Empty header value"));
}
Res.Error = "EntityTokenInvalid";
Res.ErrorCode = 1335;
Res.ErrorMessage = "EntityTokenInvalid";
var token = _authTokenService.Validate(headerValue);
if (token != null)
{
var identity = new ClaimsIdentity(token.Claims, Options.AuthenticationType);
var identities = new List<ClaimsIdentity> { identity };
var principal = new ClaimsPrincipal(identities);
if (principal.FindAuthType() != Type)
{
return Task.FromResult(AuthenticateResult.Fail("Invalid auth type"));
}
var ticket = new AuthenticationTicket(principal, Options.Scheme);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
return Task.FromResult(AuthenticateResult.Fail("Invalid JWT"));
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
Response.StatusCode = 401;
Response.ContentType = "application/json";
await Response.WriteAsync(JsonSerializer.Serialize(Res));
}
}
@@ -1,11 +1,10 @@
using Microsoft.AspNetCore.Authentication;
namespace Prospect.Server.Api.Services.Auth.Entity
namespace Prospect.Server.Api.Services.Auth.Entity;
public class EntityAuthenticationOptions : AuthenticationSchemeOptions
{
public class EntityAuthenticationOptions : AuthenticationSchemeOptions
{
public const string DefaultScheme = "EntityAuth";
public string Scheme => DefaultScheme;
public string AuthenticationType = DefaultScheme;
}
public const string DefaultScheme = "EntityAuth";
public string Scheme => DefaultScheme;
public string AuthenticationType = DefaultScheme;
}
@@ -1,34 +1,33 @@
using System.Security.Claims;
using Prospect.Server.Api.Exceptions;
namespace Prospect.Server.Api.Services.Auth.Extensions
namespace Prospect.Server.Api.Services.Auth.Extensions;
public static class ClaimsPrincipalExtensions
{
public static class ClaimsPrincipalExtensions
public static string FindAuthUserId(this ClaimsPrincipal principal)
{
public static string FindAuthUserId(this ClaimsPrincipal principal)
{
return Find(principal, AuthClaimTypes.UserId);
}
return Find(principal, AuthClaimTypes.UserId);
}
public static string FindAuthEntityId(this ClaimsPrincipal principal)
{
return Find(principal, AuthClaimTypes.EntityId);
}
public static string FindAuthEntityId(this ClaimsPrincipal principal)
{
return Find(principal, AuthClaimTypes.EntityId);
}
public static string FindAuthType(this ClaimsPrincipal principal)
public static string FindAuthType(this ClaimsPrincipal principal)
{
return Find(principal, AuthClaimTypes.Type);
}
private static string Find(ClaimsPrincipal principal, string claimType)
{
var claim = principal.FindFirst(claimType);
if (claim == null)
{
return Find(principal, AuthClaimTypes.Type);
throw new ProspectException($"Failed to find claim {claimType}");
}
private static string Find(ClaimsPrincipal principal, string claimType)
{
var claim = principal.FindFirst(claimType);
if (claim == null)
{
throw new ProspectException($"Failed to find claim {claimType}");
}
return claim.Value;
}
return claim.Value;
}
}
@@ -1,85 +1,79 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Prospect.Server.Api.Models.Client;
using Prospect.Server.Api.Services.Auth.Extensions;
namespace Prospect.Server.Api.Services.Auth.User
namespace Prospect.Server.Api.Services.Auth.User;
public class UserAuthenticationHandler : AuthenticationHandler<UserAuthenticationOptions>
{
public class UserAuthenticationHandler : AuthenticationHandler<UserAuthenticationOptions>
private const string Header = "X-Authorization";
private const string Type = AuthType.User;
private readonly AuthTokenService _authTokenService;
public UserAuthenticationHandler(
IOptionsMonitor<UserAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
AuthTokenService authTokenService) : base(options, logger, encoder, clock)
{
private const string Header = "X-Authorization";
private const string Type = AuthType.User;
_authTokenService = authTokenService;
}
private readonly AuthTokenService _authTokenService;
public UserAuthenticationHandler(
IOptionsMonitor<UserAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
AuthTokenService authTokenService) : base(options, logger, encoder, clock)
{
_authTokenService = authTokenService;
}
private string Failure { get; set; } = "Not Authenticated";
private string Failure { get; set; } = "Not Authenticated";
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.TryGetValue(Header, out var headerValues) || headerValues.Count == 0)
{
if (!Request.Headers.TryGetValue(Header, out var headerValues) || headerValues.Count == 0)
{
return Task.FromResult(AuthenticateResult.Fail("No header"));
}
return Task.FromResult(AuthenticateResult.Fail("No header"));
}
var headerValue = headerValues.FirstOrDefault();
var headerValue = headerValues.FirstOrDefault();
if (string.IsNullOrWhiteSpace(headerValue))
{
return Task.FromResult(AuthenticateResult.Fail("Empty header value"));
}
Failure = "X-Authentication HTTP header contains invalid ticket";
var token = _authTokenService.Validate(headerValue);
if (token != null)
{
var identity = new ClaimsIdentity(token.Claims, Options.AuthenticationType);
var identities = new List<ClaimsIdentity> { identity };
var principal = new ClaimsPrincipal(identities);
if (principal.FindAuthType() != Type)
{
return Task.FromResult(AuthenticateResult.Fail("Invalid auth type"));
}
var ticket = new AuthenticationTicket(principal, Options.Scheme);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
return Task.FromResult(AuthenticateResult.Fail("Invalid JWT"));
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
if (string.IsNullOrWhiteSpace(headerValue))
{
Response.StatusCode = 401;
Response.ContentType = "application/json";
await Response.WriteAsync(JsonSerializer.Serialize(new ClientResponse
{
Code = 401,
Status = "Unauthorized",
Error = "NotAuthenticated",
ErrorCode = 1074,
ErrorMessage = Failure
}));
return Task.FromResult(AuthenticateResult.Fail("Empty header value"));
}
Failure = "X-Authentication HTTP header contains invalid ticket";
var token = _authTokenService.Validate(headerValue);
if (token != null)
{
var identity = new ClaimsIdentity(token.Claims, Options.AuthenticationType);
var identities = new List<ClaimsIdentity> { identity };
var principal = new ClaimsPrincipal(identities);
if (principal.FindAuthType() != Type)
{
return Task.FromResult(AuthenticateResult.Fail("Invalid auth type"));
}
var ticket = new AuthenticationTicket(principal, Options.Scheme);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
return Task.FromResult(AuthenticateResult.Fail("Invalid JWT"));
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
Response.StatusCode = 401;
Response.ContentType = "application/json";
await Response.WriteAsync(JsonSerializer.Serialize(new ClientResponse
{
Code = 401,
Status = "Unauthorized",
Error = "NotAuthenticated",
ErrorCode = 1074,
ErrorMessage = Failure
}));
}
}
@@ -1,11 +1,10 @@
using Microsoft.AspNetCore.Authentication;
namespace Prospect.Server.Api.Services.Auth.User
namespace Prospect.Server.Api.Services.Auth.User;
public class UserAuthenticationOptions : AuthenticationSchemeOptions
{
public class UserAuthenticationOptions : AuthenticationSchemeOptions
{
public const string DefaultScheme = "UserAuth";
public string Scheme => DefaultScheme;
public string AuthenticationType = DefaultScheme;
}
public const string DefaultScheme = "UserAuth";
public string Scheme => DefaultScheme;
public string AuthenticationType = DefaultScheme;
}
@@ -2,19 +2,18 @@
using MongoDB.Driver;
using Prospect.Server.Api.Config;
namespace Prospect.Server.Api.Services.Database
{
public abstract class BaseDbService<T>
{
protected BaseDbService(IOptions<DatabaseSettings> options, string collection)
{
var settings = options.Value;
var client = new MongoClient(settings.ConnectionString);
var database = client.GetDatabase(settings.DatabaseName);
namespace Prospect.Server.Api.Services.Database;
Collection = database.GetCollection<T>(collection);
}
protected IMongoCollection<T> Collection { get; }
public abstract class BaseDbService<T>
{
protected BaseDbService(IOptions<DatabaseSettings> options, string collection)
{
var settings = options.Value;
var client = new MongoClient(settings.ConnectionString);
var database = client.GetDatabase(settings.DatabaseName);
Collection = database.GetCollection<T>(collection);
}
protected IMongoCollection<T> Collection { get; }
}
@@ -1,38 +1,36 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using Prospect.Server.Api.Config;
using Prospect.Server.Api.Services.Database.Models;
namespace Prospect.Server.Api.Services.Database
namespace Prospect.Server.Api.Services.Database;
public class DbEntityService : BaseDbService<PlayFabEntity>
{
public class DbEntityService : BaseDbService<PlayFabEntity>
public DbEntityService(IOptions<DatabaseSettings> settings) : base(settings, nameof(PlayFabEntity))
{
public DbEntityService(IOptions<DatabaseSettings> settings) : base(settings, nameof(PlayFabEntity))
{
}
}
public async Task<PlayFabEntity> FindAsync(string userId)
{
return await Collection.Find(user => user.UserId == userId).SingleOrDefaultAsync();
}
public async Task<PlayFabEntity> FindAsync(string userId)
{
return await Collection.Find(user => user.UserId == userId).SingleOrDefaultAsync();
}
private async Task<PlayFabEntity> CreateAsync(string userId)
private async Task<PlayFabEntity> CreateAsync(string userId)
{
var user = new PlayFabEntity
{
var user = new PlayFabEntity
{
UserId = userId
};
UserId = userId
};
await Collection.InsertOneAsync(user);
await Collection.InsertOneAsync(user);
return user;
}
return user;
}
public async Task<PlayFabEntity> FindOrCreateAsync(string userId)
{
return await FindAsync(userId) ??
await CreateAsync(userId);
}
public async Task<PlayFabEntity> FindOrCreateAsync(string userId)
{
return await FindAsync(userId) ??
await CreateAsync(userId);
}
}
@@ -1,60 +1,57 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using Prospect.Server.Api.Config;
using Prospect.Server.Api.Services.Database.Models;
namespace Prospect.Server.Api.Services.Database
namespace Prospect.Server.Api.Services.Database;
public class DbUserDataService : BaseDbService<PlayFabUserData>
{
public class DbUserDataService : BaseDbService<PlayFabUserData>
public DbUserDataService(IOptions<DatabaseSettings> options) : base(options, nameof(PlayFabUserData))
{
public DbUserDataService(IOptions<DatabaseSettings> options) : base(options, nameof(PlayFabUserData))
{
}
}
public async Task<bool> HasAsync(string playFabId, string key)
{
return await Collection.Find(data => data.PlayFabId == playFabId && data.Key == key).AnyAsync();
}
public async Task<bool> HasAsync(string playFabId, string key)
{
return await Collection.Find(data => data.PlayFabId == playFabId && data.Key == key).AnyAsync();
}
public async Task<PlayFabUserData> FindAsync(string playFabId, string key)
{
return await Collection.Find(data => data.PlayFabId == playFabId && data.Key == key).SingleOrDefaultAsync();
}
public async Task<PlayFabUserData> FindAsync(string playFabId, string key)
{
return await Collection.Find(data => data.PlayFabId == playFabId && data.Key == key).SingleOrDefaultAsync();
}
public async Task<PlayFabUserData> InsertAsync(string playFabId, string key, string value, bool isPublic)
public async Task<PlayFabUserData> InsertAsync(string playFabId, string key, string value, bool isPublic)
{
var data = new PlayFabUserData
{
var data = new PlayFabUserData
{
PlayFabId = playFabId,
Key = key,
Value = value,
Public = isPublic,
LastUpdated = DateTime.UtcNow
};
PlayFabId = playFabId,
Key = key,
Value = value,
Public = isPublic,
LastUpdated = DateTime.UtcNow
};
await Collection.InsertOneAsync(data);
await Collection.InsertOneAsync(data);
return data;
}
return data;
}
public async Task<IAsyncCursor<PlayFabUserData>> AllAsync(string playFabId, bool publicOnly)
{
var query = publicOnly
? Collection.Find(data => data.PlayFabId == playFabId && data.Public)
: Collection.Find(data => data.PlayFabId == playFabId);
public async Task<IAsyncCursor<PlayFabUserData>> AllAsync(string playFabId, bool publicOnly)
{
var query = publicOnly
? Collection.Find(data => data.PlayFabId == playFabId && data.Public)
: Collection.Find(data => data.PlayFabId == playFabId);
return await query.ToCursorAsync();
}
return await query.ToCursorAsync();
}
public async Task UpdateValueAsync(string dataId, string value)
{
var update = Builders<PlayFabUserData>.Update
.Set(data => data.Value, value)
.Set(data => data.LastUpdated, DateTime.UtcNow);
public async Task UpdateValueAsync(string dataId, string value)
{
var update = Builders<PlayFabUserData>.Update
.Set(data => data.Value, value)
.Set(data => data.LastUpdated, DateTime.UtcNow);
await Collection.UpdateOneAsync(data => data.Id == dataId, update);
}
await Collection.UpdateOneAsync(data => data.Id == dataId, update);
}
}
@@ -1,50 +1,46 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using Prospect.Server.Api.Config;
using Prospect.Server.Api.Services.Database.Models;
namespace Prospect.Server.Api.Services.Database
namespace Prospect.Server.Api.Services.Database;
public class DbUserService : BaseDbService<PlayFabUser>
{
public class DbUserService : BaseDbService<PlayFabUser>
public DbUserService(IOptions<DatabaseSettings> settings) : base(settings, nameof(PlayFabUser))
{
public DbUserService(IOptions<DatabaseSettings> settings) : base(settings, nameof(PlayFabUser))
{
}
}
public async Task<PlayFabUser> FindAsync(PlayFabUserAuthType type, string key)
{
return await Collection.Find(user => user.Auth.Any(auth =>
auth.Type == type &&
auth.Key == key)).FirstOrDefaultAsync();
}
public async Task<PlayFabUser> FindAsync(PlayFabUserAuthType type, string key)
{
return await Collection.Find(user => user.Auth.Any(auth =>
auth.Type == type &&
auth.Key == key)).FirstOrDefaultAsync();
}
private async Task<PlayFabUser> CreateAsync(PlayFabUserAuthType type, string key)
private async Task<PlayFabUser> CreateAsync(PlayFabUserAuthType type, string key)
{
var user = new PlayFabUser
{
var user = new PlayFabUser
DisplayName = "Unknown",
Auth = new List<PlayFabUserAuth>
{
DisplayName = "Unknown",
Auth = new List<PlayFabUserAuth>
new PlayFabUserAuth
{
new PlayFabUserAuth
{
Type = type,
Key = key
}
Type = type,
Key = key
}
};
}
};
await Collection.InsertOneAsync(user);
await Collection.InsertOneAsync(user);
return user;
}
return user;
}
public async Task<PlayFabUser> FindOrCreateAsync(PlayFabUserAuthType type, string key)
{
return await FindAsync(type, key) ??
await CreateAsync(type, key);
}
public async Task<PlayFabUser> FindOrCreateAsync(PlayFabUserAuthType type, string key)
{
return await FindAsync(type, key) ??
await CreateAsync(type, key);
}
}
@@ -1,23 +1,21 @@
using System;
using System.Security.Cryptography;
using System.Security.Cryptography;
using MongoDB.Bson.Serialization;
namespace Prospect.Server.Api.Services.Database.Generator
{
public class PlayFabIdGenerator : IIdGenerator
{
private static readonly RNGCryptoServiceProvider Random = new RNGCryptoServiceProvider();
public object GenerateId(object container, object document)
{
Span<byte> data = stackalloc byte[8];
Random.GetBytes(data);
return Convert.ToHexString(data);
}
namespace Prospect.Server.Api.Services.Database.Generator;
public bool IsEmpty(object id)
{
return id is not string idStr || string.IsNullOrEmpty(idStr);
}
public class PlayFabIdGenerator : IIdGenerator
{
private static readonly RNGCryptoServiceProvider Random = new RNGCryptoServiceProvider();
public object GenerateId(object container, object document)
{
Span<byte> data = stackalloc byte[8];
Random.GetBytes(data);
return Convert.ToHexString(data);
}
public bool IsEmpty(object id)
{
return id is not string idStr || string.IsNullOrEmpty(idStr);
}
}
@@ -1,18 +1,17 @@
using MongoDB.Bson.Serialization.Attributes;
using Prospect.Server.Api.Services.Database.Generator;
namespace Prospect.Server.Api.Services.Database.Models
namespace Prospect.Server.Api.Services.Database.Models;
/// <summary>
/// title_player_account
/// </summary>
public class PlayFabEntity
{
/// <summary>
/// title_player_account
/// </summary>
public class PlayFabEntity
{
[BsonId(IdGenerator = typeof(PlayFabIdGenerator))]
public string Id { get; set; }
[BsonId(IdGenerator = typeof(PlayFabIdGenerator))]
public string Id { get; set; }
[BsonRequired]
[BsonElement("UserId")]
public string UserId { get; set; }
}
[BsonRequired]
[BsonElement("UserId")]
public string UserId { get; set; }
}
@@ -1,23 +1,21 @@
using System.Collections.Generic;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Attributes;
using Prospect.Server.Api.Services.Database.Generator;
namespace Prospect.Server.Api.Services.Database.Models
namespace Prospect.Server.Api.Services.Database.Models;
/// <summary>
/// master_player_account
/// </summary>
public class PlayFabUser
{
/// <summary>
/// master_player_account
/// </summary>
public class PlayFabUser
{
[BsonId(IdGenerator = typeof(PlayFabIdGenerator))]
public string Id { get; set; }
[BsonId(IdGenerator = typeof(PlayFabIdGenerator))]
public string Id { get; set; }
[BsonRequired]
[BsonElement("DisplayName")]
public string DisplayName { get; set; }
[BsonRequired]
[BsonElement("DisplayName")]
public string DisplayName { get; set; }
[BsonRequired]
[BsonElement("Auth")]
public List<PlayFabUserAuth> Auth { get; set; }
}
[BsonRequired]
[BsonElement("Auth")]
public List<PlayFabUserAuth> Auth { get; set; }
}
@@ -1,13 +1,12 @@
using MongoDB.Bson.Serialization.Attributes;
namespace Prospect.Server.Api.Services.Database.Models
namespace Prospect.Server.Api.Services.Database.Models;
public class PlayFabUserAuth
{
public class PlayFabUserAuth
{
[BsonElement("Type")]
public PlayFabUserAuthType Type { get; set; }
[BsonElement("Type")]
public PlayFabUserAuthType Type { get; set; }
[BsonElement("Key")]
public string Key { get; set; }
}
[BsonElement("Key")]
public string Key { get; set; }
}
@@ -1,8 +1,7 @@
namespace Prospect.Server.Api.Services.Database.Models
namespace Prospect.Server.Api.Services.Database.Models;
public enum PlayFabUserAuthType
{
public enum PlayFabUserAuthType
{
Epic,
Steam
}
Epic,
Steam
}
@@ -1,36 +1,34 @@
using System;
using MongoDB.Bson;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Prospect.Server.Api.Services.Database.Models
{
public class PlayFabUserData
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
/// <summary>
/// PlayFabUser.Id
/// </summary>
[BsonRequired]
[BsonElement("PlayFabId")]
public string PlayFabId { get; set; }
namespace Prospect.Server.Api.Services.Database.Models;
[BsonRequired]
[BsonElement("Key")]
public string Key { get; set; }
public class PlayFabUserData
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonRequired]
[BsonElement("Value")]
public string Value { get; set; }
/// <summary>
/// PlayFabUser.Id
/// </summary>
[BsonRequired]
[BsonElement("PlayFabId")]
public string PlayFabId { get; set; }
[BsonRequired]
[BsonElement("Key")]
public string Key { get; set; }
[BsonRequired]
[BsonElement("Public")]
public bool Public { get; set; }
[BsonRequired]
[BsonElement("Value")]
public string Value { get; set; }
[BsonRequired]
[BsonElement("LastUpdated")]
public DateTime LastUpdated { get; set; }
}
[BsonRequired]
[BsonElement("Public")]
public bool Public { get; set; }
[BsonRequired]
[BsonElement("LastUpdated")]
public DateTime LastUpdated { get; set; }
}
File diff suppressed because one or more lines are too long
@@ -1,33 +1,29 @@
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
namespace Prospect.Server.Api.Services.UserData;
namespace Prospect.Server.Api.Services.UserData
public class TitleDataService
{
public class TitleDataService
{
public TitleDataService(ILogger<TitleDataService> logger)
{
public TitleDataService(ILogger<TitleDataService> logger)
{
}
}
public Dictionary<string, string> Find(List<string> keys)
{
if (keys != null && keys.Count > 0)
{
var result = new Dictionary<string, string>();
public Dictionary<string, string> Find(List<string> keys)
{
if (keys != null && keys.Count > 0)
{
var result = new Dictionary<string, string>();
foreach (var key in keys)
{
if (TitleDataDefault.Data.TryGetValue(key, out var value))
{
result.Add(key, value);
}
}
foreach (var key in keys)
{
if (TitleDataDefault.Data.TryGetValue(key, out var value))
{
result.Add(key, value);
}
}
return result;
}
return result;
}
return TitleDataDefault.Data;
}
}
return TitleDataDefault.Data;
}
}
@@ -1,182 +1,177 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Prospect.Server.Api.Models.Client.Data;
using Prospect.Server.Api.Models.Client.Data;
using Prospect.Server.Api.Services.Database;
namespace Prospect.Server.Api.Services.UserData
namespace Prospect.Server.Api.Services.UserData;
public class UserDataService
{
public class UserDataService
private readonly ILogger<UserDataService> _logger;
private readonly DbUserDataService _dbUserDataService;
public UserDataService(ILogger<UserDataService> logger, DbUserDataService dbUserDataService)
{
private readonly ILogger<UserDataService> _logger;
private readonly DbUserDataService _dbUserDataService;
_logger = logger;
_dbUserDataService = dbUserDataService;
}
public UserDataService(ILogger<UserDataService> logger, DbUserDataService dbUserDataService)
/// <summary>
/// Initialize data for the given PlayFabId.
/// </summary>
public async Task InitAsync(string playFabId)
{
// TODO: Proper objects.
var defaultData = new Dictionary<string, (bool isPublic, string value)>
{
_logger = logger;
_dbUserDataService = dbUserDataService;
["Generators__2021_09_09"] = (true, "[{\"generatorId\":\"playerquarters_gen_aurum\",\"lastClaimTime\":{\"seconds\":0}},{\"generatorId\":\"playerquarters_gen_kmarks\",\"lastClaimTime\":{\"seconds\":0}},{\"generatorId\":\"playerquarters_gen_crate\",\"lastClaimTime\":{\"seconds\":0}}]"),
["InventoryInfo"] = (true, "{\"inventoryStashLimit\":75,\"inventoryBagLimit\":300,\"inventorySafeLimit\":5}"),
["LOADOUT"] = (true, $"{{\"id\":\"\",\"userId\":\"{playFabId}\",\"kit\":\"\",\"shield\":\"\",\"helmet\":\"\",\"weaponOne\":\"\",\"weaponTwo\":\"\",\"bag\":null,\"bagItemsAsJsonStr\":\"\",\"safeItemsAsJsonStr\":\"\"}}"),
["OnboardingProgression"] = (true, "{\"currentMissionID\":\"TalkToBadum\",\"progress\":0,\"showPopup\":true}"),
["PickaxeUpgradeLevel"] = (true, "0"),
["PlayerQuartersLevel"] = (true, "{\"level\":1,\"upgradeStartedTime\":{\"seconds\":0}}"),
};
foreach (var (key, (isPublic, value)) in defaultData)
{
if (!await _dbUserDataService.HasAsync(playFabId, key))
{
await _dbUserDataService.InsertAsync(playFabId, key, value, isPublic);
}
}
}
/// <summary>
///
/// </summary>
/// <param name="currentUserId">
/// The authenticated user id.
/// </param>
/// <param name="requestUserId">
/// The requested user id.
/// </param>
/// <param name="keys"></param>
/// <returns></returns>
public async Task<Dictionary<string, FUserDataRecord>> FindAsync(
string currentUserId,
string requestUserId,
List<string> keys)
{
if (string.IsNullOrEmpty(currentUserId))
{
throw new ArgumentNullException(nameof(currentUserId));
}
if (requestUserId == null)
{
requestUserId = currentUserId;
}
/// <summary>
/// Initialize data for the given PlayFabId.
/// </summary>
public async Task InitAsync(string playFabId)
var other = currentUserId != requestUserId;
var result = new Dictionary<string, FUserDataRecord>();
if (keys != null && keys.Count > 0)
{
// TODO: Proper objects.
var defaultData = new Dictionary<string, (bool isPublic, string value)>
foreach (var key in keys)
{
["Generators__2021_09_09"] = (true, "[{\"generatorId\":\"playerquarters_gen_aurum\",\"lastClaimTime\":{\"seconds\":0}},{\"generatorId\":\"playerquarters_gen_kmarks\",\"lastClaimTime\":{\"seconds\":0}},{\"generatorId\":\"playerquarters_gen_crate\",\"lastClaimTime\":{\"seconds\":0}}]"),
["InventoryInfo"] = (true, "{\"inventoryStashLimit\":75,\"inventoryBagLimit\":300,\"inventorySafeLimit\":5}"),
["LOADOUT"] = (true, $"{{\"id\":\"\",\"userId\":\"{playFabId}\",\"kit\":\"\",\"shield\":\"\",\"helmet\":\"\",\"weaponOne\":\"\",\"weaponTwo\":\"\",\"bag\":null,\"bagItemsAsJsonStr\":\"\",\"safeItemsAsJsonStr\":\"\"}}"),
["OnboardingProgression"] = (true, "{\"currentMissionID\":\"TalkToBadum\",\"progress\":0,\"showPopup\":true}"),
["PickaxeUpgradeLevel"] = (true, "0"),
["PlayerQuartersLevel"] = (true, "{\"level\":1,\"upgradeStartedTime\":{\"seconds\":0}}"),
};
foreach (var (key, (isPublic, value)) in defaultData)
{
if (!await _dbUserDataService.HasAsync(playFabId, key))
var data = await _dbUserDataService.FindAsync(requestUserId, key);
if (data == null)
{
await _dbUserDataService.InsertAsync(playFabId, key, value, isPublic);
// TODO: Error?
continue;
}
}
}
/// <summary>
///
/// </summary>
/// <param name="currentUserId">
/// The authenticated user id.
/// </param>
/// <param name="requestUserId">
/// The requested user id.
/// </param>
/// <param name="keys"></param>
/// <returns></returns>
public async Task<Dictionary<string, FUserDataRecord>> FindAsync(
string currentUserId,
string requestUserId,
List<string> keys)
{
if (string.IsNullOrEmpty(currentUserId))
{
throw new ArgumentNullException(nameof(currentUserId));
}
if (requestUserId == null)
{
requestUserId = currentUserId;
}
var other = currentUserId != requestUserId;
var result = new Dictionary<string, FUserDataRecord>();
if (keys != null && keys.Count > 0)
{
foreach (var key in keys)
if (!data.Public && other)
{
var data = await _dbUserDataService.FindAsync(requestUserId, key);
if (data == null)
{
// TODO: Error?
continue;
}
if (!data.Public && other)
{
// TODO: Error?
continue;
}
// TODO: Error?
continue;
}
result.Add(data.Key, new FUserDataRecord
{
LastUpdated = data.LastUpdated,
Permission = data.Public ? UserDataPermission.Public : UserDataPermission.Private,
Value = data.Value
});
}
}
else
{
var cursor = await _dbUserDataService.AllAsync(requestUserId, other);
while (await cursor.MoveNextAsync())
{
foreach (var data in cursor.Current)
{
result.Add(data.Key, new FUserDataRecord
{
LastUpdated = data.LastUpdated,
Permission = data.Public ? UserDataPermission.Public : UserDataPermission.Private,
Value = data.Value
});
}
}
else
{
var cursor = await _dbUserDataService.AllAsync(requestUserId, other);
while (await cursor.MoveNextAsync())
{
foreach (var data in cursor.Current)
{
result.Add(data.Key, new FUserDataRecord
{
LastUpdated = data.LastUpdated,
Permission = data.Public ? UserDataPermission.Public : UserDataPermission.Private,
Value = data.Value
});
}
}
}
return result;
}
/// <summary>
///
/// </summary>
/// <param name="currentUserId">
/// The authenticated user id.
/// </param>
/// <param name="requestUserId">
/// The requested user id.
/// </param>
/// <param name="changes"></param>
/// <returns></returns>
public async Task UpdateAsync(
string currentUserId,
string requestUserId,
Dictionary<string, string> changes)
{
if (changes.Count == 0)
{
return;
}
if (string.IsNullOrEmpty(currentUserId))
{
throw new ArgumentNullException(nameof(currentUserId));
}
if (requestUserId == null)
{
requestUserId = currentUserId;
}
return result;
}
// Whether we are updating someone else.
var other = currentUserId != requestUserId;
/// <summary>
///
/// </summary>
/// <param name="currentUserId">
/// The authenticated user id.
/// </param>
/// <param name="requestUserId">
/// The requested user id.
/// </param>
/// <param name="changes"></param>
/// <returns></returns>
public async Task UpdateAsync(
string currentUserId,
string requestUserId,
Dictionary<string, string> changes)
{
if (changes.Count == 0)
{
return;
}
foreach (var (key, value) in changes)
if (string.IsNullOrEmpty(currentUserId))
{
throw new ArgumentNullException(nameof(currentUserId));
}
if (requestUserId == null)
{
requestUserId = currentUserId;
}
// Whether we are updating someone else.
var other = currentUserId != requestUserId;
foreach (var (key, value) in changes)
{
var data = await _dbUserDataService.FindAsync(currentUserId, key);
if (data == null)
{
var data = await _dbUserDataService.FindAsync(currentUserId, key);
if (data == null)
if (other)
{
if (other)
{
_logger.LogWarning("User {PlayFabId} attempted to update non-existing key {Key} of another user {PlayFabIdOther}", currentUserId, key, requestUserId);
}
else
{
await _dbUserDataService.InsertAsync(currentUserId, key, value, false);
}
_logger.LogWarning("User {PlayFabId} attempted to update non-existing key {Key} of another user {PlayFabIdOther}", currentUserId, key, requestUserId);
}
else
{
await _dbUserDataService.InsertAsync(currentUserId, key, value, false);
}
continue;
}
if (!data.Public && other)
{
_logger.LogWarning("User {PlayFabId} attempted to update non-public key {Key} of another user {PlayFabIdOther}", currentUserId, key, data.PlayFabId);
continue;
}
await _dbUserDataService.UpdateValueAsync(data.Id, value);
continue;
}
if (!data.Public && other)
{
_logger.LogWarning("User {PlayFabId} attempted to update non-public key {Key} of another user {PlayFabIdOther}", currentUserId, key, data.PlayFabId);
continue;
}
await _dbUserDataService.UpdateValueAsync(data.Id, value);
}
}
}