Skip to content

Commit

Permalink
Merge pull request #61 from ksummarized/00016_Implement_OAuth_Google
Browse files Browse the repository at this point in the history
Closes #16 Implement o auth Google
  • Loading branch information
Sojusan authored Mar 5, 2024
2 parents b8be8e9 + 91d6f77 commit 0d4e7de
Show file tree
Hide file tree
Showing 56 changed files with 15,317 additions and 12,224 deletions.
2 changes: 0 additions & 2 deletions .env

This file was deleted.

53 changes: 53 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# PostgreSQL

# The name of the database user
POSTGRES_USER=
# The password for the database user
POSTGRES_PASSWORD=

# Keycloak

# The username of the keycloak admin user
KEYCLOAK_ADMIN=
# The password of the keycloak admin user
KEYCLOAK_ADMIN_PASSWORD=
# If enable the health-check endpoints in the keycloak
KC_HEALTH_ENABLED=true
# The name of the database provider for the keycloak (we are using our PostgreSQL database from the container)
KC_DB=postgres
# The URL connection to the keycloak database
# E.g: jdbc:postgresql://<database_container>:<database_port>/keycloak
KC_DB_URL=
# The name of the user that keycloak will use to connect into the database
KC_DB_USERNAME=
# The password of the user that keycloak will use to connect into the database
KC_DB_PASSWORD=

# Keycloak app (realm-export.json)

# The name of the realm in the keycloak that will be used by application
KSUMMARIZED_REALM_NAME=KnowledgeSummarized
# The secret for the `ksummarized` client that is used by frontend application during authentication
KSUMMARIZED_CLIENT_SECRET=
# The private key of the ksummarized realm
KSUMMARIZED_RSA_PRIVATE_KEY=
# The public key of the ksummarized realm
KSUMMARIZED_RSA_PUBLIC_KEY=
# The certificate of the ksummarized realm
KSUMMARIZED_RSA_CERTIFICATE=
# The ClientId for the Google OAuth provider
PROVIDER_GOOGLE_ID=
# The ClientSecret for the Google OAuth provider
PROVIDER_GOOGLE_SECRET=
# The ClientId for the Twitter/X OAuth provider
PROVIDER_TWITTER_X_ID=
# The ClientSecret for the Twitter/X OAuth provider
PROVIDER_TWITTER_X_SECRET=
# The ClientId for the GitHub OAuth provider
PROVIDER_GITHUB_ID=
# The ClientSecret for the GitHub OAuth provider
PROVIDER_GITHUB_SECRET=
# The ClientId for the Facebook OAuth provider
PROVIDER_FACEBOOK_ID=
# The ClientSecret for the Facebook OAuth provider
PROVIDER_FACEBOOK_SECRET=
4 changes: 2 additions & 2 deletions .github/workflows/sonarcloud-backend-build-and-analyze.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:
name: Build and analyze
runs-on: windows-latest
steps:
- name: Set up JDK 11
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: 11
java-version: 17
distribution: "zulu" # Alternative distribution options are available.
- uses: actions/checkout@v3
with:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -455,3 +455,7 @@ node_modules
dist
dist-ssr
*.local

# Environment files
.env
appsettings.Development.json
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ To run this application locally it is recommended to have the following installe
- dotnet sdk
- entity framework tools

Firstly there is a need to configure environment variables:

1. Copy `.env.example` as `.env` and populate the environment variables.
1. Copy `appsettings.json` as `appsettings.Development.json` and populate the variables.

Next install dev-certs to use https in powershell

```powershell
dotnet dev-certs https -ep ".aspnet\https\aspnetapp.pfx" -p devcertpasswd --trust
```

or in bash/zsh

```bash
dotnet dev-certs https -ep .aspnet/https/aspnetapp.pfx -p devcertpasswd --trust
```

Next go to the `scripts` directory and run `apply_migrations.ps1`
Next You should go back to the main directory and run `docker compose up --build`
This can be done with the following snippet.
Expand Down
2 changes: 1 addition & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/aspnet:7.0.2-alpine3.17-amd64 AS base
FROM mcr.microsoft.com/dotnet/aspnet:7.0.10-alpine3.18 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
Expand Down
6 changes: 6 additions & 0 deletions backend/src/api/Constants/ErrorMessages.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace api.Constants;

public static class ErrorMessages
{
public const string UserAlreadyExists = "User already exists";
}
92 changes: 35 additions & 57 deletions backend/src/api/Controllers/AuthenticationController.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using api.Data.DTO;
using api.Constants;
using api.Data.DTO;
using api.Services.Users;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.IdentityModel.Tokens.Jwt;

namespace api.Controllers;

Expand All @@ -10,77 +12,53 @@ namespace api.Controllers;
public class AuthenticationController : ControllerBase
{
private readonly IUserService _userService;
private readonly ILogger<AuthenticationController> _logger;

public AuthenticationController(IUserService userService)
public AuthenticationController(IUserService userService, ILogger<AuthenticationController> logger)
{
_userService = userService;
_logger = logger;
}

[HttpPost("register")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
public async Task<IActionResult> Register([FromBody] UserDTO user)
[HttpGet("create-user")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateUser()
{
if (!ModelState.IsValid) { return BadRequest("Invalid data provided!"); }
var accessToken = Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
var jsonTokenData = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
var keycloakUuid = jsonTokenData.Subject;
var userEmail = jsonTokenData.Claims.FirstOrDefault(claim => claim.Type == "email")?.Value;

var (IsSuccess, Error) = await _userService.Register(user);
if (IsSuccess)
if (userEmail == null)
{
return Ok("User created");
}
else
{
return BadRequest(Error);
_logger.LogInformation("User with keycloakUuid={keycloakUuid} tried to log in without email.", keycloakUuid);
return BadRequest("Required data missing");
}
}

[HttpPost("login")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
[ProducesResponseType(401)]
public async Task<IActionResult> Login([FromBody] UserDTO user)
{
if (!ModelState.IsValid) { return BadRequest("Please provide login credentials!"); }
var (isSuccess, error) = await _userService.CreateKeycloakUser(
new UserDTO
{
KeycloakUuid = keycloakUuid,
Email = userEmail
}
);

var (IsSuccess, AuthResult, Error) = await _userService.Login(user);
if (IsSuccess)
if (isSuccess)
{
return Ok(AuthResult);
}
else
{
return Unauthorized(Error);
}
}

[HttpPost("refresh-token")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
[ProducesResponseType(401)]
public async Task<IActionResult> RefreshToken([FromBody] TokenRequestDTO tokenRequestDTO)
{
if (!ModelState.IsValid) { return BadRequest("Invalid token request."); }
var (IsSuccess, AuthResult, Error) = await _userService.RefreshLogin(tokenRequestDTO);
if (IsSuccess)
{
return Ok(AuthResult);
_logger.LogInformation("The user {email} has been created successfully.", userEmail);
return Ok("User created");
}
else
{
return Unauthorized(Error);
}
}

[HttpPost("logout")]
[ProducesResponseType(200)]
[Authorize()]
public async Task<IActionResult> Logout()
{
if (HttpContext.User?.Identity?.Name is not null)
{
await _userService.Logout(HttpContext.User.Identity.Name);
if (error == ErrorMessages.UserAlreadyExists)
{
_logger.LogInformation("The user {email} has already been created.", userEmail);
return Ok("User is already created");
}
_logger.LogInformation("Failure during creation of {email} user.", userEmail);
return BadRequest(error);
}
return Ok("The user has been logged out.");
}

}
4 changes: 2 additions & 2 deletions backend/src/api/Controllers/GreetingsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ public class GreetingsController : ControllerBase
{
[HttpGet]
[Authorize]
[ProducesResponseType(200)]
[ProducesResponseType(StatusCodes.Status200OK)]
[Route("user")]
public IActionResult Greet()
{
return Ok($"Hello {HttpContext?.User?.Identity?.Name ?? "World" }");
}

[HttpGet]
[ProducesResponseType(200)]
[ProducesResponseType(StatusCodes.Status200OK)]
[Route("HelloWorld")]
public IActionResult HelloWorld()
{
Expand Down
13 changes: 13 additions & 0 deletions backend/src/api/Data/ApplicationDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using api.Data.DAO;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace api.Data;

public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
public DbSet<UserModel> Users { get; set; }
}
19 changes: 0 additions & 19 deletions backend/src/api/Data/DAO/RefreshToken.cs

This file was deleted.

9 changes: 7 additions & 2 deletions backend/src/api/Data/DAO/UserModel.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;

namespace api.Data.DAO;

public class UserModel : IdentityUser
public class UserModel
{
[Key]
public int Id { get; set; }
public required string KeycloakUuid { get; set; }
[EmailAddress]
public required string Email { get; set; }
}
10 changes: 0 additions & 10 deletions backend/src/api/Data/DTO/AuthResultDTO.cs

This file was deleted.

12 changes: 0 additions & 12 deletions backend/src/api/Data/DTO/TokenRequestDTO.cs

This file was deleted.

7 changes: 3 additions & 4 deletions backend/src/api/Data/DTO/UserDTO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ namespace api.Data.DTO;
public class UserDTO
{
[Required]
[EmailAddress]
public string Email { get; set; }

public required string KeycloakUuid { get; set; }
[Required]
public string Password { get; set; }
[EmailAddress]
public required string Email { get; set; }
}
7 changes: 0 additions & 7 deletions backend/src/api/Data/JwtOptions.cs

This file was deleted.

17 changes: 17 additions & 0 deletions backend/src/api/Data/KeycloakJwtOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Diagnostics.CodeAnalysis;

namespace api.Data;
public class KeycloakJwtOptions
{
public required string Issuer { get; set; }
public required string Audience { get; set; }
public required string Secret { get; set; }

[SetsRequiredMembers]
public KeycloakJwtOptions(string issuer, string audience, string secret)
{
Issuer = issuer;
Audience = audience;
Secret = secret;
}
}
13 changes: 0 additions & 13 deletions backend/src/api/Data/UsersDbContext.cs

This file was deleted.

Loading

0 comments on commit 0d4e7de

Please sign in to comment.