aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author杨宇千 <crupest@outlook.com>2019-04-11 21:13:15 +0800
committerGitHub <noreply@github.com>2019-04-11 21:13:15 +0800
commit19cae15eba2bcede41b818e1b8ab7fd5ac92eb05 (patch)
tree34118752ae3015a26c0f6f3a02b3043806ce895a
parent5b5ca3acb1b9decb5ad13798dc79ba2d58f2ce95 (diff)
parent63573d7c988e0bc1b7e82eeea53ecc3678b0a2f5 (diff)
downloadtimeline-19cae15eba2bcede41b818e1b8ab7fd5ac92eb05.tar.gz
timeline-19cae15eba2bcede41b818e1b8ab7fd5ac92eb05.tar.bz2
timeline-19cae15eba2bcede41b818e1b8ab7fd5ac92eb05.zip
Merge pull request #19 from crupest/18-createtoken
Change create token api.
-rw-r--r--Timeline.Tests/AuthorizationUnitTest.cs4
-rw-r--r--Timeline.Tests/Helpers/Authentication/AuthenticationHttpClientExtensions.cs13
-rw-r--r--Timeline.Tests/JwtTokenUnitTest.cs57
-rw-r--r--Timeline/ClientApp/src/app/user/internal-user-service/errors.ts16
-rw-r--r--Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts5
-rw-r--r--Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts1
-rw-r--r--Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts29
-rw-r--r--Timeline/Controllers/UserController.cs49
-rw-r--r--Timeline/Entities/Token.cs26
-rw-r--r--Timeline/Services/JwtService.cs26
-rw-r--r--Timeline/Timeline.csproj4
11 files changed, 116 insertions, 114 deletions
diff --git a/Timeline.Tests/AuthorizationUnitTest.cs b/Timeline.Tests/AuthorizationUnitTest.cs
index 1566f2ac..2693366c 100644
--- a/Timeline.Tests/AuthorizationUnitTest.cs
+++ b/Timeline.Tests/AuthorizationUnitTest.cs
@@ -1,10 +1,6 @@
using Microsoft.AspNetCore.Mvc.Testing;
-using Newtonsoft.Json;
-using System;
using System.Net;
-using System.Net.Http;
using System.Threading.Tasks;
-using Timeline.Controllers;
using Timeline.Tests.Helpers;
using Timeline.Tests.Helpers.Authentication;
using Xunit;
diff --git a/Timeline.Tests/Helpers/Authentication/AuthenticationHttpClientExtensions.cs b/Timeline.Tests/Helpers/Authentication/AuthenticationHttpClientExtensions.cs
index a4cb8c65..ccb2a372 100644
--- a/Timeline.Tests/Helpers/Authentication/AuthenticationHttpClientExtensions.cs
+++ b/Timeline.Tests/Helpers/Authentication/AuthenticationHttpClientExtensions.cs
@@ -1,11 +1,9 @@
using Newtonsoft.Json;
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
-using Timeline.Controllers;
+using Timeline.Entities;
using Xunit;
namespace Timeline.Tests.Helpers.Authentication
@@ -14,13 +12,16 @@ namespace Timeline.Tests.Helpers.Authentication
{
private const string CreateTokenUrl = "/api/User/CreateToken";
- public static async Task<UserController.CreateTokenResult> CreateUserTokenAsync(this HttpClient client, string username, string password)
+ public static async Task<CreateTokenResponse> CreateUserTokenAsync(this HttpClient client, string username, string password, bool assertSuccess = true)
{
- var response = await client.PostAsJsonAsync(CreateTokenUrl, new UserController.UserCredentials { Username = username, Password = password });
+ var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = username, Password = password });
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var result = JsonConvert.DeserializeObject<UserController.CreateTokenResult>(await response.Content.ReadAsStringAsync());
+ var result = JsonConvert.DeserializeObject<CreateTokenResponse>(await response.Content.ReadAsStringAsync());
+
+ if (assertSuccess)
+ Assert.True(result.Success);
return result;
}
diff --git a/Timeline.Tests/JwtTokenUnitTest.cs b/Timeline.Tests/JwtTokenUnitTest.cs
index e55bc82c..7e881895 100644
--- a/Timeline.Tests/JwtTokenUnitTest.cs
+++ b/Timeline.Tests/JwtTokenUnitTest.cs
@@ -1,14 +1,8 @@
using Microsoft.AspNetCore.Mvc.Testing;
using Newtonsoft.Json;
-using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Net;
using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
-using Timeline.Controllers;
-using Timeline.Services;
+using Timeline.Entities;
using Timeline.Tests.Helpers;
using Timeline.Tests.Helpers.Authentication;
using Xunit;
@@ -18,6 +12,7 @@ namespace Timeline.Tests
{
public class JwtTokenUnitTest : IClassFixture<WebApplicationFactory<Startup>>
{
+ private const string CreateTokenUrl = "/api/User/CreateToken";
private const string ValidateTokenUrl = "/api/User/ValidateToken";
private readonly WebApplicationFactory<Startup> _factory;
@@ -28,53 +23,59 @@ namespace Timeline.Tests
}
[Fact]
- public async void ValidateToken_BadTokenTest()
+ public async void CreateTokenTest_BadCredential()
{
using (var client = _factory.CreateDefaultClient())
{
- var response = await client.PostAsync(ValidateTokenUrl, new StringContent("bad token hahaha", Encoding.UTF8, "text/plain"));
-
+ var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = "???", Password = "???" });
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- var validationInfo = JsonConvert.DeserializeObject<TokenValidationResult>(await response.Content.ReadAsStringAsync());
-
- Assert.False(validationInfo.IsValid);
- Assert.Null(validationInfo.UserInfo);
+ var result = JsonConvert.DeserializeObject<CreateTokenResponse>(await response.Content.ReadAsStringAsync());
+ Assert.False(result.Success);
+ Assert.Null(result.Token);
+ Assert.Null(result.UserInfo);
}
}
[Fact]
- public async void ValidateToken_PlainTextGoodTokenTest()
+ public async void CreateTokenTest_GoodCredential()
{
using (var client = _factory.CreateDefaultClient())
{
- var createTokenResult = await client.CreateUserTokenAsync("admin", "admin");
+ var response = await client.PostAsJsonAsync(CreateTokenUrl, new CreateTokenRequest { Username = "user", Password = "user" });
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var result = JsonConvert.DeserializeObject<CreateTokenResponse>(await response.Content.ReadAsStringAsync());
+ Assert.True(result.Success);
+ Assert.NotNull(result.Token);
+ Assert.NotNull(result.UserInfo);
+ }
+ }
- var response = await client.PostAsync(ValidateTokenUrl, new StringContent(createTokenResult.Token, Encoding.UTF8, "text/plain"));
+ [Fact]
+ public async void ValidateTokenTest_BadToken()
+ {
+ using (var client = _factory.CreateDefaultClient())
+ {
+ var response = await client.PostAsJsonAsync(ValidateTokenUrl, new TokenValidationRequest { Token = "bad token hahaha" });
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var result = JsonConvert.DeserializeObject<TokenValidationResult>(await response.Content.ReadAsStringAsync());
+ var validationInfo = JsonConvert.DeserializeObject<TokenValidationResponse>(await response.Content.ReadAsStringAsync());
- Assert.True(result.IsValid);
- Assert.NotNull(result.UserInfo);
- Assert.Equal(createTokenResult.UserInfo.Username, result.UserInfo.Username);
- Assert.Equal(createTokenResult.UserInfo.Roles, result.UserInfo.Roles);
+ Assert.False(validationInfo.IsValid);
+ Assert.Null(validationInfo.UserInfo);
}
}
[Fact]
- public async void ValidateToken_JsonGoodTokenTest()
+ public async void ValidateTokenTest_GoodToken()
{
using (var client = _factory.CreateDefaultClient())
{
var createTokenResult = await client.CreateUserTokenAsync("admin", "admin");
- var response = await client.PostAsJsonAsync(ValidateTokenUrl, new UserController.TokenValidationRequest { Token = createTokenResult.Token });
-
+ var response = await client.PostAsJsonAsync(ValidateTokenUrl, new TokenValidationRequest { Token = createTokenResult.Token });
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- var result = JsonConvert.DeserializeObject<TokenValidationResult>(await response.Content.ReadAsStringAsync());
+ var result = JsonConvert.DeserializeObject<TokenValidationResponse>(await response.Content.ReadAsStringAsync());
Assert.True(result.IsValid);
Assert.NotNull(result.UserInfo);
diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/errors.ts b/Timeline/ClientApp/src/app/user/internal-user-service/errors.ts
index 22e44dd6..3358a9d9 100644
--- a/Timeline/ClientApp/src/app/user/internal-user-service/errors.ts
+++ b/Timeline/ClientApp/src/app/user/internal-user-service/errors.ts
@@ -1,25 +1,29 @@
-export abstract class LoginError extends Error { }
-
-export class BadNetworkError extends LoginError {
+export class BadNetworkError extends Error {
constructor() {
super('Network is bad.');
}
}
-export class AlreadyLoginError extends LoginError {
+export class AlreadyLoginError extends Error {
constructor() {
super('Internal logical error. There is already a token saved. Please call validateUserLoginState first.');
}
}
-export class BadCredentialsError extends LoginError {
+export class BadCredentialsError extends Error {
constructor() {
super('Username or password is wrong.');
}
}
-export class UnknownError extends LoginError {
+export class UnknownError extends Error {
constructor(public internalError?: any) {
super('Sorry, unknown error occured!');
}
}
+
+export class ServerInternalError extends Error {
+ constructor(message?: string) {
+ super('Wrong server response. ' + message);
+ }
+}
diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts b/Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts
index 5664cf7c..f52233c9 100644
--- a/Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts
+++ b/Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts
@@ -6,8 +6,9 @@ export const validateTokenUrl = '/api/User/ValidateToken';
export type CreateTokenRequest = UserCredentials;
export interface CreateTokenResponse {
- token: string;
- userInfo: UserInfo;
+ success: boolean;
+ token?: string;
+ userInfo?: UserInfo;
}
export interface ValidateTokenRequest {
diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts
index 6906ed60..15755382 100644
--- a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts
+++ b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts
@@ -102,6 +102,7 @@ describe('InternalUserService', () => {
request.url === createTokenUrl && request.body !== null &&
request.body.username === mockUserCredentials.username &&
request.body.password === mockUserCredentials.password).flush(<CreateTokenResponse>{
+ success: true,
token: mockToken,
userInfo: mockUserInfo
});
diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts
index 6de355f2..66eafde9 100644
--- a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts
+++ b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts
@@ -5,7 +5,7 @@ import { Router } from '@angular/router';
import { Observable, throwError, BehaviorSubject, of } from 'rxjs';
import { map, catchError, retry, switchMap, tap, filter } from 'rxjs/operators';
-import { AlreadyLoginError, BadCredentialsError, BadNetworkError, UnknownError } from './errors';
+import { AlreadyLoginError, BadCredentialsError, BadNetworkError, UnknownError, ServerInternalError } from './errors';
import {
createTokenUrl, validateTokenUrl, CreateTokenRequest,
CreateTokenResponse, ValidateTokenRequest, ValidateTokenResponse
@@ -84,7 +84,7 @@ export class InternalUserService {
if (userInfo) {
return of(userInfo);
} else {
- return throwError(new Error('Wrong server response. IsValid is true but UserInfo is null.'));
+ return throwError(new ServerInternalError('IsValid is true but UserInfo is null.'));
}
} else {
return of(null);
@@ -117,21 +117,28 @@ export class InternalUserService {
if (error.error instanceof ErrorEvent) {
console.error('An error occurred when login: ' + error.error.message);
return throwError(new BadNetworkError());
- } else if (error.status === 400) {
- console.error('An error occurred when login: wrong credentials.');
- return throwError(new BadCredentialsError());
} else {
console.error('An unknown error occurred when login: ' + error);
return throwError(new UnknownError(error));
}
}),
- map(result => {
- this.token = result.token;
- if (info.rememberMe) {
- this.window.localStorage.setItem(TOKEN_STORAGE_KEY, result.token);
+ switchMap(result => {
+ if (result.success) {
+ if (result.token && result.userInfo) {
+ this.token = result.token;
+ if (info.rememberMe) {
+ this.window.localStorage.setItem(TOKEN_STORAGE_KEY, result.token);
+ }
+ this.userInfoSubject.next(result.userInfo);
+ return of(result.userInfo);
+ } else {
+ console.error('An error occurred when login: server return wrong data.');
+ return throwError(new ServerInternalError('Token or userInfo is null.'));
+ }
+ } else {
+ console.error('An error occurred when login: wrong credentials.');
+ return throwError(new BadCredentialsError());
}
- this.userInfoSubject.next(result.userInfo);
- return result.userInfo;
})
);
}
diff --git a/Timeline/Controllers/UserController.cs b/Timeline/Controllers/UserController.cs
index 45242ce3..eb1b8513 100644
--- a/Timeline/Controllers/UserController.cs
+++ b/Timeline/Controllers/UserController.cs
@@ -15,23 +15,6 @@ namespace Timeline.Controllers
public const int LogInFailed = 4001;
}
- public class UserCredentials
- {
- public string Username { get; set; }
- public string Password { get; set; }
- }
-
- public class CreateTokenResult
- {
- public string Token { get; set; }
- public UserInfo UserInfo { get; set; }
- }
-
- public class TokenValidationRequest
- {
- public string Token { get; set; }
- }
-
private readonly IUserService _userService;
private readonly IJwtService _jwtService;
private readonly ILogger<UserController> _logger;
@@ -45,39 +28,31 @@ namespace Timeline.Controllers
[HttpPost("[action]")]
[AllowAnonymous]
- public ActionResult<CreateTokenResult> CreateToken([FromBody] UserCredentials credentials)
+ public ActionResult<CreateTokenResponse> CreateToken([FromBody] CreateTokenRequest request)
{
- var user = _userService.Authenticate(credentials.Username, credentials.Password);
+ var user = _userService.Authenticate(request.Username, request.Password);
if (user == null) {
- _logger.LogInformation(LoggingEventIds.LogInFailed, "Attemp to login with username: {} and password: {} failed.", credentials.Username, credentials.Password);
- return BadRequest();
+ _logger.LogInformation(LoggingEventIds.LogInFailed, "Attemp to login with username: {} and password: {} failed.", request.Username, request.Password);
+ return Ok(new CreateTokenResponse
+ {
+ Success = false
+ });
}
- _logger.LogInformation(LoggingEventIds.LogInSucceeded, "Login with username: {} succeeded.", credentials.Username);
+ _logger.LogInformation(LoggingEventIds.LogInSucceeded, "Login with username: {} succeeded.", request.Username);
- var result = new CreateTokenResult
+ return Ok(new CreateTokenResponse
{
+ Success = true,
Token = _jwtService.GenerateJwtToken(user),
UserInfo = user.GetUserInfo()
- };
-
- return Ok(result);
- }
-
- [HttpPost("[action]")]
- [Consumes("text/plain")]
- [AllowAnonymous]
- public ActionResult<TokenValidationResult> ValidateToken([FromBody] string token)
- {
- var result = _jwtService.ValidateJwtToken(token);
- return Ok(result);
+ });
}
[HttpPost("[action]")]
- [Consumes("application/json")]
[AllowAnonymous]
- public ActionResult<TokenValidationResult> ValidateToken([FromBody] TokenValidationRequest request)
+ public ActionResult<TokenValidationResponse> ValidateToken([FromBody] TokenValidationRequest request)
{
var result = _jwtService.ValidateJwtToken(request.Token);
return Ok(result);
diff --git a/Timeline/Entities/Token.cs b/Timeline/Entities/Token.cs
new file mode 100644
index 00000000..ce5b92ff
--- /dev/null
+++ b/Timeline/Entities/Token.cs
@@ -0,0 +1,26 @@
+namespace Timeline.Entities
+{
+ public class CreateTokenRequest
+ {
+ public string Username { get; set; }
+ public string Password { get; set; }
+ }
+
+ public class CreateTokenResponse
+ {
+ public bool Success { get; set; }
+ public string Token { get; set; }
+ public UserInfo UserInfo { get; set; }
+ }
+
+ public class TokenValidationRequest
+ {
+ public string Token { get; set; }
+ }
+
+ public class TokenValidationResponse
+ {
+ public bool IsValid { get; set; }
+ public UserInfo UserInfo { get; set; }
+ }
+}
diff --git a/Timeline/Services/JwtService.cs b/Timeline/Services/JwtService.cs
index a01f3f2b..abdde908 100644
--- a/Timeline/Services/JwtService.cs
+++ b/Timeline/Services/JwtService.cs
@@ -11,12 +11,6 @@ using Timeline.Entities;
namespace Timeline.Services
{
- public class TokenValidationResult
- {
- public bool IsValid { get; set; }
- public UserInfo UserInfo { get; set; }
- }
-
public interface IJwtService
{
/// <summary>
@@ -30,17 +24,17 @@ namespace Timeline.Services
/// <summary>
/// Validate a JWT token.
/// Return null is <paramref name="token"/> is null.
- /// If token is invalid, return a <see cref="TokenValidationResult"/> with
- /// <see cref="TokenValidationResult.IsValid"/> set to false and
- /// <see cref="TokenValidationResult.UserInfo"/> set to null.
- /// If token is valid, return a <see cref="TokenValidationResult"/> with
- /// <see cref="TokenValidationResult.IsValid"/> set to true and
- /// <see cref="TokenValidationResult.UserInfo"/> filled with the user info
+ /// If token is invalid, return a <see cref="TokenValidationResponse"/> with
+ /// <see cref="TokenValidationResponse.IsValid"/> set to false and
+ /// <see cref="TokenValidationResponse.UserInfo"/> set to null.
+ /// If token is valid, return a <see cref="TokenValidationResponse"/> with
+ /// <see cref="TokenValidationResponse.IsValid"/> set to true and
+ /// <see cref="TokenValidationResponse.UserInfo"/> filled with the user info
/// in the token.
/// </summary>
/// <param name="token">The token string to validate.</param>
/// <returns>Null if <paramref name="token"/> is null. Or the result.</returns>
- TokenValidationResult ValidateJwtToken(string token);
+ TokenValidationResponse ValidateJwtToken(string token);
}
@@ -86,7 +80,7 @@ namespace Timeline.Services
}
- public TokenValidationResult ValidateJwtToken(string token)
+ public TokenValidationResponse ValidateJwtToken(string token)
{
if (token == null)
return null;
@@ -114,7 +108,7 @@ namespace Timeline.Services
Roles = identity.FindAll(identity.RoleClaimType).Select(claim => claim.Value).ToArray()
};
- return new TokenValidationResult
+ return new TokenValidationResponse
{
IsValid = true,
UserInfo = userInfo
@@ -123,7 +117,7 @@ namespace Timeline.Services
catch (Exception e)
{
_logger.LogInformation(e, "Token validation failed! Token is {} .", token);
- return new TokenValidationResult { IsValid = false };
+ return new TokenValidationResponse { IsValid = false };
}
}
}
diff --git a/Timeline/Timeline.csproj b/Timeline/Timeline.csproj
index 330d2981..e55eb90d 100644
--- a/Timeline/Timeline.csproj
+++ b/Timeline/Timeline.csproj
@@ -27,10 +27,6 @@
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>
- <ItemGroup>
- <Folder Include="bin\" />
- </ItemGroup>
-
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">