Skip to content

Commit

Permalink
feat(GraphQL): Add GraphQL
Browse files Browse the repository at this point in the history
Added GraphQL and decided to remove most of the REST API. Login and Refresh session does happen with REST.

BREAKING CHANGE: Most of the old endpoints are now removed and need to be accessed through GraphQL, logic however remains unchanged with exception of return types.
  • Loading branch information
cyberhck authored May 30, 2021
1 parent f493374 commit b87de29
Show file tree
Hide file tree
Showing 84 changed files with 1,179 additions and 1,001 deletions.
7 changes: 0 additions & 7 deletions Micro.Auth.Api/Authentication/CustomClaims.cs

This file was deleted.

33 changes: 0 additions & 33 deletions Micro.Auth.Api/Authentication/RequirePermission.cs

This file was deleted.

17 changes: 7 additions & 10 deletions Micro.Auth.Api/Authentication/SessionController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
using Micro.Auth.Api.Authentication.ViewModels;
using Micro.Auth.Api.Internal.UserData.Extensions;
using Micro.Auth.Api.Internal.ValidationAttributes;
using Micro.Auth.Business.Measurements;
using Micro.Auth.Business.RefreshTokens;
using Micro.Auth.Business.Users;
using Micro.Auth.Business.Internal.Measurements;
using Micro.Auth.Business.Sessions;
using Micro.Auth.Storage.Exceptions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
Expand All @@ -22,16 +21,14 @@ namespace Micro.Auth.Api.Authentication
public class SessionController : ControllerBase
{
private readonly ILogger<SessionController> _logger;
private readonly IUserService _userService;
private readonly IMetrics _metrics;
private readonly IRefreshTokenService _refreshTokenService;
private readonly ISessionService _sessionService;

public SessionController(ILogger<SessionController> logger, IUserService userService, IMetrics metrics, IRefreshTokenService refreshTokenService)
public SessionController(ILogger<SessionController> logger, IMetrics metrics, ISessionService sessionService)
{
_logger = logger;
_userService = userService;
_metrics = metrics;
_refreshTokenService = refreshTokenService;
_sessionService = sessionService;
}

[HttpPost("new")]
Expand All @@ -40,7 +37,7 @@ public async Task<IActionResult> New([FromHeader(Name = "Authorization")][Requir
try
{
var (login, password) = GetBasicAuthData(authorization);
var (result, response) = await _userService.Login(new LoginRequest
var (result, response) = await _sessionService.Login(new LoginRequest
{
Login = login,
Password = password,
Expand Down Expand Up @@ -96,7 +93,7 @@ string authorization
var token = GetBearerToken(authorization);
try
{
var jwt = await _refreshTokenService.Refresh(token);
var jwt = await _sessionService.Refresh(token);
return Ok(new RefreshTokenSuccessResponse
{
Jwt = jwt
Expand Down
17 changes: 17 additions & 0 deletions Micro.Auth.Api/GraphQL/AuthSchema.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using GraphQL.Types;
using Micro.Auth.Api.GraphQL.Directives;

namespace Micro.Auth.Api.GraphQL
{
public class AuthSchema : Schema
{
public AuthSchema(IServiceProvider services, Query query, Mutation mutation) : base(services)
{
Query = query;
Mutation = mutation;
Directives.Register(new AuthorizeDirective());
RegisterVisitor(typeof(AuthorizeDirectiveVisitor));
}
}
}
29 changes: 29 additions & 0 deletions Micro.Auth.Api/GraphQL/DataLoaders/SessionByUserDataLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using GraphQL.DataLoader;
using Micro.Auth.Storage;

namespace Micro.Auth.Api.GraphQL.DataLoaders
{
public class SessionByUserDataLoader : DataLoaderBase<string, IEnumerable<RefreshToken>>
{
private readonly IRefreshTokenRepository _refreshTokenRepository;

public SessionByUserDataLoader(IRefreshTokenRepository refreshTokenRepository)
{
_refreshTokenRepository = refreshTokenRepository;
}

protected override async Task FetchAsync(IEnumerable<DataLoaderPair<string, IEnumerable<RefreshToken>>> list, CancellationToken cancellationToken)
{
var userIds = list.Select(x => x.Key);
var sessions = await _refreshTokenRepository.FindByUserIds(userIds);
foreach (var entry in list)
{
entry.SetResult(sessions[entry.Key]);
}
}
}
}
31 changes: 31 additions & 0 deletions Micro.Auth.Api/GraphQL/DataLoaders/UserByIdDataLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using GraphQL.DataLoader;
using Micro.Auth.Storage;
using User = Micro.Auth.Business.Users.User;

namespace Micro.Auth.Api.GraphQL.DataLoaders
{
public class UserByIdDataLoader : DataLoaderBase<string, User>
{
private readonly IUserRepository _userRepository;

public UserByIdDataLoader(IUserRepository userRepository)
{
_userRepository = userRepository;
}

protected override async Task FetchAsync(IEnumerable<DataLoaderPair<string, User>> list, CancellationToken cancellationToken)
{
var ids = list.Select(x => x.Key).ToList();
var users = await _userRepository.FindByIds(ids.ToArray());
foreach (var entry in list)
{
var exists = users.TryGetValue(entry.Key, out var user);
entry.SetResult(exists ? User.FromDbUser(user) : null);
}
}
}
}
47 changes: 47 additions & 0 deletions Micro.Auth.Api/GraphQL/Directives/AuthorizeDirective.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using GraphQL;
using GraphQL.Types;
using GraphQL.Utilities;
using Micro.Auth.Api.GraphQL.Directives.Exceptions;
using Microsoft.AspNetCore.Http;

namespace Micro.Auth.Api.GraphQL.Directives
{
public class AuthorizeDirective : DirectiveGraphType
{
public const string DirectiveName = "authorize";
public AuthorizeDirective() : base(
DirectiveName,
DirectiveLocation.Field,
DirectiveLocation.Mutation,
DirectiveLocation.Query,
DirectiveLocation.FieldDefinition)
{
}
}

public class AuthorizeDirectiveVisitor : BaseSchemaNodeVisitor
{
private readonly IHttpContextAccessor _contextAccessor;
public AuthorizeDirectiveVisitor(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}

public override void VisitObjectFieldDefinition(FieldType field, IObjectGraphType type, ISchema schema)
{
var applied = field.FindAppliedDirective(AuthorizeDirective.DirectiveName);
if (applied == null)
{
return;
}

var isAuthenticated = _contextAccessor.HttpContext?.User.Identity?.IsAuthenticated;
if (isAuthenticated == true)
{
return;
}

throw new NotAuthorizedException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace Micro.Auth.Api.GraphQL.Directives.Exceptions
{
public class NotAuthorizedException : Exception
{
public NotAuthorizedException() : base("This operation requires logging in")
{
}
}
}
18 changes: 18 additions & 0 deletions Micro.Auth.Api/GraphQL/Directives/Extensions/Directives.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using GraphQL;
using GraphQL.Builders;
using GraphQL.Types;

namespace Micro.Auth.Api.GraphQL.Directives.Extensions
{
public static class Directives
{
public static FieldType Authorize(this FieldType type)
{
return type.ApplyDirective(AuthorizeDirective.DirectiveName);
}
public static FieldBuilder<TSourceType, TReturnType> Authorize<TSourceType, TReturnType>(this FieldBuilder<TSourceType, TReturnType> type)
{
return type.Directive(AuthorizeDirective.DirectiveName);
}
}
}
19 changes: 19 additions & 0 deletions Micro.Auth.Api/GraphQL/Inputs/ChangePasswordInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using GraphQL.Types;

namespace Micro.Auth.Api.GraphQL.Inputs
{
public class ChangePasswordInput : InputObjectGraphType
{
public static QueryArgument BuildArgument()
{
return new QueryArgument<NonNullGraphType<ChangePasswordInput>> {Name = "input"};
}

public ChangePasswordInput()
{
Name = "ChangePasswordInput";
Field<NonNullGraphType<StringGraphType>>("old_password");
Field<NonNullGraphType<StringGraphType>>("new_password");
}
}
}
20 changes: 20 additions & 0 deletions Micro.Auth.Api/GraphQL/Inputs/RegisterInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using GraphQL.Types;

namespace Micro.Auth.Api.GraphQL.Inputs
{
public class RegisterInputType : InputObjectGraphType
{
public static QueryArgument BuildArgument()
{
return new QueryArgument<NonNullGraphType<RegisterInputType>> {Name = "input"};
}

public RegisterInputType()
{
Name = "RegisterInput";
Field<NonNullGraphType<StringGraphType>>("username");
Field<NonNullGraphType<StringGraphType>>("email");
Field<NonNullGraphType<StringGraphType>>("password");
}
}
}
20 changes: 20 additions & 0 deletions Micro.Auth.Api/GraphQL/Inputs/ResetPasswordInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using GraphQL.Types;

namespace Micro.Auth.Api.GraphQL.Inputs
{
public class ResetPasswordInput : InputObjectGraphType
{
public static QueryArgument BuildArgument()
{
return new QueryArgument<NonNullGraphType<ResetPasswordInput>> {Name = "input"};
}

public ResetPasswordInput()
{
Name = "ResetPasswordInput";
Field<NonNullGraphType<StringGraphType>>("login");
Field<NonNullGraphType<StringGraphType>>("token");
Field<NonNullGraphType<StringGraphType>>("new_password");
}
}
}
19 changes: 19 additions & 0 deletions Micro.Auth.Api/GraphQL/Inputs/VerifyEmailInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using GraphQL.Types;

namespace Micro.Auth.Api.GraphQL.Inputs
{
public class VerifyEmailInputType : InputObjectGraphType
{
public static QueryArgument BuildArgument()
{
return new QueryArgument<NonNullGraphType<VerifyEmailInputType>> {Name = "input"};
}

public VerifyEmailInputType()
{
Name = "VerifyEmailInput";
Field<NonNullGraphType<StringGraphType>>("login");
Field<NonNullGraphType<StringGraphType>>("token");
}
}
}
44 changes: 44 additions & 0 deletions Micro.Auth.Api/GraphQL/Mutation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using GraphQL;
using GraphQL.Types;
using Micro.Auth.Api.GraphQL.Directives.Extensions;
using Micro.Auth.Api.GraphQL.Inputs;
using Micro.Auth.Api.GraphQL.Types;
using Micro.Auth.Api.Internal.UserData.Extensions;
using Micro.Auth.Business.Common;
using Micro.Auth.Business.EmailVerification;
using Micro.Auth.Business.PasswordManager;
using Micro.Auth.Business.Users;
using Microsoft.AspNetCore.Http;

namespace Micro.Auth.Api.GraphQL
{
public class Mutation : ObjectGraphType
{
public Mutation(IUserService userService, IPasswordManager passwordManager, IEmailVerificationService verification, IHttpContextAccessor contextAccessor)
{
FieldAsync<NonNullGraphType<UserType>, User>("register",
arguments: new QueryArguments(RegisterInputType.BuildArgument()),
resolve: x => userService.Create(x.GetArgument<RegisterInput>("input")));

FieldAsync<NonNullGraphType<UserType>, User>("verifyEmail",
arguments: new QueryArguments(VerifyEmailInputType.BuildArgument()),
resolve: x => verification.ConfirmEmail(x.GetArgument<VerifyEmailInput>("input")));

FieldAsync<NonNullGraphType<ResultType>, Result>("sendActivationEmail",
arguments: new QueryArguments(new QueryArgument<NonNullGraphType<StringGraphType>> {Name = "login"}),
resolve: x => verification.SendActivationEmail(x.GetArgument<string>("login")));

FieldAsync<NonNullGraphType<ResultType>, Result>("requestPasswordReset",
arguments: new QueryArguments(new QueryArgument<NonNullGraphType<StringGraphType>> {Name = "login"}),
resolve: x => passwordManager.RequestPasswordReset(x.GetArgument<string>("login")));

FieldAsync<NonNullGraphType<UserType>, User>("changePassword",
arguments: new QueryArguments(ChangePasswordInput.BuildArgument()),
resolve: x => passwordManager.ChangePassword(contextAccessor.GetUserId(), x.GetArgument<ChangePasswordRequest>("input"))).Authorize();

FieldAsync<NonNullGraphType<UserType>, User>("resetPassword",
arguments: new QueryArguments(ResetPasswordInput.BuildArgument()),
resolve: x => passwordManager.ResetPassword(x.GetArgument<ResetPasswordRequest>("input")));
}
}
}
Loading

0 comments on commit b87de29

Please sign in to comment.