Build auth API
This commit is contained in:
commit
dafe603a06
43 changed files with 1153 additions and 0 deletions
48
.drone.yml
Normal file
48
.drone.yml
Normal file
|
@ -0,0 +1,48 @@
|
|||
kind: pipeline
|
||||
name: default
|
||||
|
||||
clone:
|
||||
skip_verify: true
|
||||
|
||||
volumes:
|
||||
- name: output
|
||||
host:
|
||||
path: /var/www/Api.Auth
|
||||
|
||||
steps:
|
||||
- name: build and publish
|
||||
image: mcr.microsoft.com/dotnet/core/sdk:3.1
|
||||
volumes:
|
||||
- name: output
|
||||
path: /output
|
||||
environment:
|
||||
ConnectionString:
|
||||
from_secret: ConnectionString
|
||||
commands:
|
||||
- chmod +x ./build.sh
|
||||
- ./build.sh
|
||||
- sed -i "s/<DatabaseConnectionString>/$ConnectionString/g" output/appsettings.json
|
||||
- cp api.auth.service output/
|
||||
- cp -r ./output/* /output
|
||||
- name: restart service
|
||||
privileged: true
|
||||
image: appleboy/drone-ssh
|
||||
settings:
|
||||
host: 192.168.1.3
|
||||
username:
|
||||
from_secret: ssh_user
|
||||
password:
|
||||
from_secret: ssh_password
|
||||
script:
|
||||
- systemctl daemon-reload
|
||||
- service api.auth restart
|
||||
- name: notify
|
||||
image: drillster/drone-email
|
||||
settings:
|
||||
host: 192.168.1.3
|
||||
skip_verify: true
|
||||
from: build@robware.uk
|
||||
when:
|
||||
status:
|
||||
- changed
|
||||
- failure
|
198
.editorconfig
Normal file
198
.editorconfig
Normal file
|
@ -0,0 +1,198 @@
|
|||
# Remove the line below if you want to inherit .editorconfig settings from higher directories
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
|
||||
# C# files
|
||||
[*.cs]
|
||||
max_line_length = 180
|
||||
# New line preferences
|
||||
end_of_line = crlf
|
||||
insert_final_newline = false
|
||||
|
||||
#### .NET Coding Conventions ####
|
||||
|
||||
# Organize usings
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = false
|
||||
|
||||
# this. and Me. preferences
|
||||
dotnet_style_qualification_for_event = false:silent
|
||||
dotnet_style_qualification_for_field = false:silent
|
||||
dotnet_style_qualification_for_method = false:silent
|
||||
dotnet_style_qualification_for_property = false:silent
|
||||
|
||||
# Language keywords vs BCL types preferences
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
||||
dotnet_style_predefined_type_for_member_access = true:silent
|
||||
|
||||
# Parentheses preferences
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
|
||||
|
||||
# Modifier preferences
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||
|
||||
# Expression-level preferences
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
|
||||
# Field preferences
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
|
||||
# Parameter preferences
|
||||
dotnet_code_quality_unused_parameters = all:suggestion
|
||||
|
||||
#### C# Coding Conventions ####
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_elsewhere = false:silent
|
||||
csharp_style_var_for_built_in_types = false:silent
|
||||
csharp_style_var_when_type_is_apparent = false:silent
|
||||
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_accessors = true:silent
|
||||
csharp_style_expression_bodied_constructors = false:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
csharp_style_expression_bodied_methods = false:silent
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_properties = true:silent
|
||||
|
||||
# Pattern matching preferences
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_prefer_switch_expression = true:suggestion
|
||||
|
||||
# Null-checking preferences
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
|
||||
# Modifier preferences
|
||||
csharp_prefer_static_local_function = true:suggestion
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
|
||||
|
||||
# Code-block preferences
|
||||
csharp_prefer_braces = false:silent
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
|
||||
# Expression-level preferences
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_prefer_index_operator = true:suggestion
|
||||
csharp_style_prefer_range_operator = true:suggestion
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||
|
||||
# 'using' directive preferences
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
|
||||
#### C# Formatting Rules ####
|
||||
|
||||
# New line preferences
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_open_brace = none
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
# Indentation preferences
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = true
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_indent_switch_labels = true
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
# Wrapping preferences
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
|
||||
[*.{yaml,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
src/Robware.Api.Auth/appsettings.Development.json filter=clean-config
|
2
.gitconfig
Normal file
2
.gitconfig
Normal file
|
@ -0,0 +1,2 @@
|
|||
[filter "clean-config"]
|
||||
clean = ./clean-config.sh
|
56
.gitignore
vendored
Normal file
56
.gitignore
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
*.swp
|
||||
*.*~
|
||||
project.lock.json
|
||||
.DS_Store
|
||||
*.pyc
|
||||
nupkg/
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode
|
||||
|
||||
# Rider
|
||||
.idea
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
#build/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
msbuild.log
|
||||
msbuild.err
|
||||
msbuild.wrn
|
||||
|
||||
# Visual Studio 2015
|
||||
.vs/
|
||||
|
||||
|
||||
#lcov files
|
||||
lcov.info
|
||||
|
||||
#compiled and minified assets
|
||||
*.css
|
||||
*.min.css
|
||||
*.min.js
|
||||
|
||||
#nodejs
|
||||
node_modules
|
||||
|
||||
output
|
||||
.tmp
|
||||
*.ncrunchsolution
|
||||
/_NCrunch_Website
|
||||
*.DotSettings
|
||||
*.cache
|
1
.nuke
Normal file
1
.nuke
Normal file
|
@ -0,0 +1 @@
|
|||
src/Robware.Api.Auth.sln
|
9
Readme.md
Normal file
9
Readme.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
[](https://build.robware.uk/Robware/Api.Auth)
|
||||
|
||||
# Auth microservice
|
||||
|
||||
Provides an API to authenticate users.
|
||||
|
||||
## Setup
|
||||
|
||||
After clone, please run `git config --local include.path ../.gitconfig`. This will set up the filters required to ignore local dev config.
|
16
api.auth.service
Normal file
16
api.auth.service
Normal file
|
@ -0,0 +1,16 @@
|
|||
[Unit]
|
||||
Description=Robware Auth API
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/var/www/Api.Auth
|
||||
ExecStart=/usr/bin/dotnet /var/www/Api.Auth/Robware.Api.Auth.dll
|
||||
Restart=always
|
||||
# Restart service after 10 seconds if the dotnet service crashes:
|
||||
RestartSec=10
|
||||
KillSignal=SIGINT
|
||||
SyslogIdentifier=Api.Auth
|
||||
User=www-data
|
||||
Environment=ASPNETCORE_ENVIRONMENT=Production
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
68
build.ps1
Normal file
68
build.ps1
Normal file
|
@ -0,0 +1,68 @@
|
|||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
|
||||
[string[]]$BuildArguments
|
||||
)
|
||||
|
||||
Write-Output "Windows PowerShell $($Host.Version)"
|
||||
|
||||
Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { exit 1 }
|
||||
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
|
||||
|
||||
###########################################################################
|
||||
# CONFIGURATION
|
||||
###########################################################################
|
||||
|
||||
$BuildProjectFile = "$PSScriptRoot\build\_build.csproj"
|
||||
$TempDirectory = "$PSScriptRoot\\.tmp"
|
||||
|
||||
$DotNetGlobalFile = "$PSScriptRoot\\global.json"
|
||||
$DotNetInstallUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.ps1"
|
||||
$DotNetChannel = "Current"
|
||||
|
||||
$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1
|
||||
$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
|
||||
|
||||
###########################################################################
|
||||
# EXECUTION
|
||||
###########################################################################
|
||||
|
||||
function ExecSafe([scriptblock] $cmd) {
|
||||
& $cmd
|
||||
if ($LASTEXITCODE) { exit $LASTEXITCODE }
|
||||
}
|
||||
|
||||
# If global.json exists, load expected version
|
||||
if (Test-Path $DotNetGlobalFile) {
|
||||
$DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
|
||||
if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
|
||||
$DotNetVersion = $DotNetGlobal.sdk.version
|
||||
}
|
||||
}
|
||||
|
||||
# If dotnet is installed locally, and expected version is not set or installation matches the expected version
|
||||
if ((Get-Command "dotnet" -ErrorAction SilentlyContinue) -ne $null -and `
|
||||
(!(Test-Path variable:DotNetVersion) -or $(& dotnet --version) -eq $DotNetVersion)) {
|
||||
$env:DOTNET_EXE = (Get-Command "dotnet").Path
|
||||
}
|
||||
else {
|
||||
$DotNetDirectory = "$TempDirectory\dotnet-win"
|
||||
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
|
||||
|
||||
# Download install script
|
||||
$DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
|
||||
md -force $TempDirectory > $null
|
||||
(New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
|
||||
|
||||
# Install by channel or version
|
||||
if (!(Test-Path variable:DotNetVersion)) {
|
||||
ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
|
||||
} else {
|
||||
ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)"
|
||||
|
||||
ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false }
|
||||
ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }
|
62
build.sh
Normal file
62
build.sh
Normal file
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
echo $(bash --version 2>&1 | head -n 1)
|
||||
|
||||
set -eo pipefail
|
||||
SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
||||
|
||||
###########################################################################
|
||||
# CONFIGURATION
|
||||
###########################################################################
|
||||
|
||||
BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj"
|
||||
TEMP_DIRECTORY="$SCRIPT_DIR//.tmp"
|
||||
|
||||
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
|
||||
DOTNET_INSTALL_URL="https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.sh"
|
||||
DOTNET_CHANNEL="Current"
|
||||
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
|
||||
|
||||
###########################################################################
|
||||
# EXECUTION
|
||||
###########################################################################
|
||||
|
||||
function FirstJsonValue {
|
||||
perl -nle 'print $1 if m{"'$1'": "([^"\-]+)",?}' <<< ${@:2}
|
||||
}
|
||||
|
||||
# If global.json exists, load expected version
|
||||
if [ -f "$DOTNET_GLOBAL_FILE" ]; then
|
||||
DOTNET_VERSION=$(FirstJsonValue "version" $(cat "$DOTNET_GLOBAL_FILE"))
|
||||
if [ "$DOTNET_VERSION" == "" ]; then
|
||||
unset DOTNET_VERSION
|
||||
fi
|
||||
fi
|
||||
|
||||
# If dotnet is installed locally, and expected version is not set or installation matches the expected version
|
||||
if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version) == "$DOTNET_VERSION") ]]; then
|
||||
export DOTNET_EXE="$(command -v dotnet)"
|
||||
else
|
||||
DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
|
||||
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
|
||||
|
||||
# Download install script
|
||||
DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
|
||||
mkdir -p "$TEMP_DIRECTORY"
|
||||
curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
|
||||
chmod +x "$DOTNET_INSTALL_FILE"
|
||||
|
||||
# Install by channel or version
|
||||
if [ -z ${DOTNET_VERSION+x} ]; then
|
||||
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
|
||||
else
|
||||
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
|
||||
|
||||
"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false
|
||||
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"
|
10
build/.editorconfig
Normal file
10
build/.editorconfig
Normal file
|
@ -0,0 +1,10 @@
|
|||
[*.cs]
|
||||
dotnet_style_qualification_for_field = false:warning
|
||||
dotnet_style_qualification_for_property = false:warning
|
||||
dotnet_style_qualification_for_method = false:warning
|
||||
dotnet_style_qualification_for_event = false:warning
|
||||
dotnet_style_require_accessibility_modifiers = never:warning
|
||||
|
||||
csharp_style_expression_bodied_properties = true:warning
|
||||
csharp_style_expression_bodied_indexers = true:warning
|
||||
csharp_style_expression_bodied_accessors = true:warning
|
62
build/Build.cs
Normal file
62
build/Build.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
using Nuke.Common;
|
||||
using Nuke.Common.Execution;
|
||||
using Nuke.Common.IO;
|
||||
using Nuke.Common.ProjectModel;
|
||||
using Nuke.Common.Tools.DotNet;
|
||||
using Nuke.Common.Utilities.Collections;
|
||||
using static Nuke.Common.IO.FileSystemTasks;
|
||||
using static Nuke.Common.Tools.DotNet.DotNetTasks;
|
||||
|
||||
[CheckBuildProjectConfigurations]
|
||||
[UnsetVisualStudioEnvironmentVariables]
|
||||
class Build : NukeBuild {
|
||||
|
||||
public static int Main() => Execute<Build>(x => x.Publish);
|
||||
|
||||
[Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
|
||||
readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
|
||||
|
||||
[Solution] readonly Solution Solution;
|
||||
|
||||
AbsolutePath SourceDirectory => RootDirectory / "src";
|
||||
AbsolutePath OutputDirectory => RootDirectory / "output";
|
||||
|
||||
Target Clean => _ => _
|
||||
.Before(Restore)
|
||||
.Executes(() => {
|
||||
SourceDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory);
|
||||
EnsureCleanDirectory(OutputDirectory);
|
||||
});
|
||||
|
||||
Target Restore => _ => _
|
||||
.Executes(() => {
|
||||
DotNetRestore(s => s
|
||||
.SetProjectFile(Solution));
|
||||
});
|
||||
|
||||
Target Compile => _ => _
|
||||
.DependsOn(Restore)
|
||||
.Executes(() => {
|
||||
DotNetBuild(s => s
|
||||
.SetProjectFile(Solution)
|
||||
.SetConfiguration(Configuration)
|
||||
.EnableNoRestore());
|
||||
});
|
||||
|
||||
Target Test => _ => _
|
||||
.DependsOn(Compile)
|
||||
.Executes(() => {
|
||||
DotNetTest(s => s
|
||||
.SetProjectFile(Solution)
|
||||
.EnableNoRestore());
|
||||
});
|
||||
|
||||
Target Publish => _ => _
|
||||
.DependsOn(Test)
|
||||
.Executes(() => {
|
||||
DotNetPublish(s => s
|
||||
.SetProject(SourceDirectory / "Robware.Api.Auth/Robware.Api.Auth.csproj")
|
||||
.SetConfiguration(Configuration)
|
||||
.SetOutput(OutputDirectory));
|
||||
});
|
||||
}
|
34
build/_build.csproj
Normal file
34
build/_build.csproj
Normal file
|
@ -0,0 +1,34 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<RootNamespace></RootNamespace>
|
||||
<IsPackable>False</IsPackable>
|
||||
<NoWarn>CS0649;CS0169</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Nuke.Common" Version="0.24.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<NukeSpecificationFiles Include="**\*.json" Exclude="bin\**;obj\**" />
|
||||
<NukeExternalFiles Include="**\*.*.ext" Exclude="bin\**;obj\**" />
|
||||
<None Remove="*.csproj.DotSettings;*.ref.*.txt" />
|
||||
|
||||
<!-- Common build related files -->
|
||||
<None Include="..\build.ps1" />
|
||||
<None Include="..\build.sh" />
|
||||
<None Include="..\.nuke" />
|
||||
<None Include="..\global.json" Condition="Exists('..\global.json')" />
|
||||
<None Include="..\nuget.config" Condition="Exists('..\nuget.config')" />
|
||||
<None Include="..\azure-pipelines.yml" Condition="Exists('..\azure-pipelines.yml')" />
|
||||
<None Include="..\Jenkinsfile" Condition="Exists('..\Jenkinsfile')" />
|
||||
<None Include="..\appveyor.yml" Condition="Exists('..\appveyor.yml')" />
|
||||
<None Include="..\.travis.yml" Condition="Exists('..\.travis.yml')" />
|
||||
<None Include="..\GitVersion.yml" Condition="Exists('..\GitVersion.yml')" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
5
clean-config.sh
Normal file
5
clean-config.sh
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
sed \
|
||||
-e 's/"database": "Server=.*;User ID=.*;Password=.*;Database=.*"/"database": "Server=localhost;User ID=user;Password=pass;Database=db"/g' \
|
||||
$1
|
67
src/Robware.Api.Auth.Tests/AuthControllerTests.cs
Normal file
67
src/Robware.Api.Auth.Tests/AuthControllerTests.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Robware.Api.Auth.Controllers;
|
||||
using Robware.Api.Auth.Models;
|
||||
using Robware.Auth;
|
||||
using Xunit;
|
||||
|
||||
namespace Robware.Api.Auth.Tests {
|
||||
public class AuthControllerTests {
|
||||
private class TestUser : User {
|
||||
public TestUser(string username, string password) {
|
||||
Username = username;
|
||||
Password = password;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Authenticate_WithSuccessfulLoginRequest_ReturnsUser() {
|
||||
var logger = Substitute.For<ILogger<AuthController>>();
|
||||
var authenticator = Substitute.For<IAuthenticator>();
|
||||
authenticator.Authenticate("username", "password").Returns((AuthenticationResult.Success, new TestUser("username", "password")));
|
||||
|
||||
var request = new LoginRequest {
|
||||
Username = "username",
|
||||
Password = "password"
|
||||
};
|
||||
|
||||
var expectation = new TestUser("username", "password");
|
||||
|
||||
var controller = new AuthController(logger, authenticator);
|
||||
(await controller.Authenticate(request)).Value.Should().BeEquivalentTo(expectation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Authenticate_WithIncorrectPassword_Returns401() {
|
||||
var logger = Substitute.For<ILogger<AuthController>>();
|
||||
var authenticator = Substitute.For<IAuthenticator>();
|
||||
authenticator.Authenticate("username", "password").Returns((AuthenticationResult.IncorrectPassword, null));
|
||||
|
||||
var request = new LoginRequest {
|
||||
Username = "username",
|
||||
Password = "password"
|
||||
};
|
||||
|
||||
var controller = new AuthController(logger, authenticator);
|
||||
(await controller.Authenticate(request)).Result.Should().BeOfType<UnauthorizedResult>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Authenticate_WithIncorrectPassword_Returns404() {
|
||||
var logger = Substitute.For<ILogger<AuthController>>();
|
||||
var authenticator = Substitute.For<IAuthenticator>();
|
||||
authenticator.Authenticate("username", "password").Returns((AuthenticationResult.NotFound, null));
|
||||
|
||||
var request = new LoginRequest {
|
||||
Username = "username",
|
||||
Password = "password"
|
||||
};
|
||||
|
||||
var controller = new AuthController(logger, authenticator);
|
||||
(await controller.Authenticate(request)).Result.Should().BeOfType<NotFoundResult>();
|
||||
}
|
||||
}
|
||||
}
|
22
src/Robware.Api.Auth.Tests/Robware.Api.Auth.Tests.csproj
Normal file
22
src/Robware.Api.Auth.Tests/Robware.Api.Auth.Tests.csproj
Normal file
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="5.10.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="NSubstitute" Version="4.2.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Robware.Api.Auth\Robware.Api.Auth.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
53
src/Robware.Api.Auth.sln
Normal file
53
src/Robware.Api.Auth.sln
Normal file
|
@ -0,0 +1,53 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29613.14
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Robware.Api.Auth", "Robware.Api.Auth\Robware.Api.Auth.csproj", "{D21F1402-6526-4BD7-8ADA-F8DC626D6D5A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robware.Api.Auth.Tests", "Robware.Api.Auth.Tests\Robware.Api.Auth.Tests.csproj", "{88716F73-0264-44D9-970D-4134C644C7EF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robware.Auth", "Robware.Auth\Robware.Auth.csproj", "{8740FE72-12D7-4039-9EB3-0417E529A10E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robware.Auth.Tests", "Robware.Auth.Tests\Robware.Auth.Tests.csproj", "{E229DE31-8DBB-4AED-9461-A04C8DE0F074}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robware.Data", "Robware.Data\Robware.Data.csproj", "{69989FA2-BEE8-491D-97B9-856D4916D154}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "..\build\_build.csproj", "{19A36DA9-BFBF-4988-B7C7-4808D6B57246}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{19A36DA9-BFBF-4988-B7C7-4808D6B57246}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{19A36DA9-BFBF-4988-B7C7-4808D6B57246}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D21F1402-6526-4BD7-8ADA-F8DC626D6D5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D21F1402-6526-4BD7-8ADA-F8DC626D6D5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D21F1402-6526-4BD7-8ADA-F8DC626D6D5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D21F1402-6526-4BD7-8ADA-F8DC626D6D5A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{88716F73-0264-44D9-970D-4134C644C7EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{88716F73-0264-44D9-970D-4134C644C7EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{88716F73-0264-44D9-970D-4134C644C7EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{88716F73-0264-44D9-970D-4134C644C7EF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8740FE72-12D7-4039-9EB3-0417E529A10E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8740FE72-12D7-4039-9EB3-0417E529A10E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8740FE72-12D7-4039-9EB3-0417E529A10E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8740FE72-12D7-4039-9EB3-0417E529A10E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E229DE31-8DBB-4AED-9461-A04C8DE0F074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E229DE31-8DBB-4AED-9461-A04C8DE0F074}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E229DE31-8DBB-4AED-9461-A04C8DE0F074}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E229DE31-8DBB-4AED-9461-A04C8DE0F074}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{69989FA2-BEE8-491D-97B9-856D4916D154}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{69989FA2-BEE8-491D-97B9-856D4916D154}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{69989FA2-BEE8-491D-97B9-856D4916D154}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{69989FA2-BEE8-491D-97B9-856D4916D154}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {75A89B46-CAE8-45F8-9BEF-3B7A6FD0BC72}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
35
src/Robware.Api.Auth/Controllers/AuthController.cs
Normal file
35
src/Robware.Api.Auth/Controllers/AuthController.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Robware.Api.Auth.Models;
|
||||
using Robware.Auth;
|
||||
|
||||
namespace Robware.Api.Auth.Controllers {
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class AuthController : ControllerBase {
|
||||
private readonly ILogger<AuthController> _logger;
|
||||
private readonly IAuthenticator _authenticator;
|
||||
|
||||
public AuthController(ILogger<AuthController> logger, IAuthenticator authenticator) {
|
||||
_logger = logger;
|
||||
_authenticator = authenticator;
|
||||
}
|
||||
|
||||
[HttpPost(nameof(Authenticate))]
|
||||
public async Task<ActionResult<User>> Authenticate(LoginRequest request) {
|
||||
var (result, user) = await _authenticator.Authenticate(request.Username, request.Password);
|
||||
switch (result) {
|
||||
case AuthenticationResult.Success:
|
||||
return user;
|
||||
case AuthenticationResult.NotFound:
|
||||
return NotFound();
|
||||
case AuthenticationResult.IncorrectPassword:
|
||||
return Unauthorized();
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
6
src/Robware.Api.Auth/Models/LoginRequest.cs
Normal file
6
src/Robware.Api.Auth/Models/LoginRequest.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Robware.Api.Auth.Models {
|
||||
public class LoginRequest {
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
12
src/Robware.Api.Auth/Program.cs
Normal file
12
src/Robware.Api.Auth/Program.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Robware.Api.Auth {
|
||||
public class Program {
|
||||
public static void Main(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder => {
|
||||
webBuilder.UseStartup<Startup>();
|
||||
}).Build().Run();
|
||||
}
|
||||
}
|
28
src/Robware.Api.Auth/Properties/launchSettings.json
Normal file
28
src/Robware.Api.Auth/Properties/launchSettings.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:61069",
|
||||
"sslPort": 44309
|
||||
}
|
||||
},
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Robware.Api.Auth": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "weatherforecast",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000"
|
||||
}
|
||||
}
|
||||
}
|
13
src/Robware.Api.Auth/Robware.Api.Auth.csproj
Normal file
13
src/Robware.Api.Auth/Robware.Api.Auth.csproj
Normal file
|
@ -0,0 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Robware.Auth\Robware.Auth.csproj" />
|
||||
<ProjectReference Include="..\Robware.Data\Robware.Data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
44
src/Robware.Api.Auth/Startup.cs
Normal file
44
src/Robware.Api.Auth/Startup.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Robware.Auth;
|
||||
using Robware.Data;
|
||||
|
||||
namespace Robware.Api.Auth {
|
||||
public class Startup {
|
||||
public Startup(IConfiguration configuration) {
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services) {
|
||||
services.AddControllers();
|
||||
|
||||
services.AddSingleton<ICryptographyProvider, CryptographyProvider>()
|
||||
.AddSingleton<IAuthenticator, Authenticator>()
|
||||
.AddSingleton<IDatabaseProvider>(new MySQLDatabaseProvider(Configuration.GetConnectionString("database")))
|
||||
.AddSingleton<IUsers, UserRepository>();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
|
||||
if (env.IsDevelopment()) {
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints => {
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
12
src/Robware.Api.Auth/appsettings.Development.json
Normal file
12
src/Robware.Api.Auth/appsettings.Development.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"database": "Server=localhost;User ID=user;Password=pass;Database=db"
|
||||
}
|
||||
}
|
20
src/Robware.Api.Auth/appsettings.json
Normal file
20
src/Robware.Api.Auth/appsettings.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"Kestrel": {
|
||||
"EndPoints": {
|
||||
"Http": {
|
||||
"Url": "http://0.0.0.0:5003"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"database": "<DatabaseConnectionString>"
|
||||
}
|
||||
}
|
50
src/Robware.Auth.Tests/AuthenticatorTests.cs
Normal file
50
src/Robware.Auth.Tests/AuthenticatorTests.cs
Normal file
|
@ -0,0 +1,50 @@
|
|||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ExceptionExtensions;
|
||||
using Robware.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace Robware.Auth.Tests {
|
||||
public class AuthenticatorTests {
|
||||
[Fact]
|
||||
public async Task Authenticate_ForUserThatExistsWithCorrectPassword_ReturnsOkResultWithUser() {
|
||||
var users = Substitute.For<IUsers>();
|
||||
var crypto = Substitute.For<ICryptographyProvider>();
|
||||
crypto.Encrypt("password").Returns("password");
|
||||
|
||||
var user = new TestUser("test", "password");
|
||||
users.GetByEmail("test").Returns(user);
|
||||
|
||||
var auth = new Authenticator(users, crypto);
|
||||
(await auth.Authenticate("test", "password")).Should().BeEquivalentTo((AuthenticationResult.Success, user));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Authenticate_ForUserThatExistsWithIncorrectPassword_ReturnsIncorrectPassword() {
|
||||
var users = Substitute.For<IUsers>();
|
||||
var crypto = Substitute.For<ICryptographyProvider>();
|
||||
crypto.Encrypt("password").Returns("password");
|
||||
|
||||
var user = new TestUser("test", "password");
|
||||
users.GetByEmail("test").Returns(user);
|
||||
|
||||
var auth = new Authenticator(users, crypto);
|
||||
|
||||
(await auth.Authenticate("test", "wrong")).Should().BeEquivalentTo((AuthenticationResult.IncorrectPassword, null as User));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Authenticate_ForUserThatDoesntExist_ReturnsNotFound() {
|
||||
var users = Substitute.For<IUsers>();
|
||||
users.GetByEmail("test").Throws(new UserNotFoundException(""));
|
||||
|
||||
var crypto = Substitute.For<ICryptographyProvider>();
|
||||
crypto.Encrypt("password").Returns("password");
|
||||
|
||||
var auth = new Authenticator(users, crypto);
|
||||
|
||||
(await auth.Authenticate("test", "password")).Should().BeEquivalentTo((AuthenticationResult.NotFound, null as User));
|
||||
}
|
||||
}
|
||||
}
|
12
src/Robware.Auth.Tests/CryoptographyProviderTests.cs
Normal file
12
src/Robware.Auth.Tests/CryoptographyProviderTests.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace Robware.Auth.Tests {
|
||||
public class CryoptographyProviderTests {
|
||||
[Fact]
|
||||
public void Encrypt_WithInput_ReturnsHash() {
|
||||
var provider = new CryptographyProvider();
|
||||
provider.Encrypt("password").Should().Be("5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8");
|
||||
}
|
||||
}
|
||||
}
|
23
src/Robware.Auth.Tests/Robware.Auth.Tests.csproj
Normal file
23
src/Robware.Auth.Tests/Robware.Auth.Tests.csproj
Normal file
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="5.10.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="NSubstitute" Version="4.2.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Robware.Auth\Robware.Auth.csproj" />
|
||||
<ProjectReference Include="..\Robware.Data\Robware.Data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
8
src/Robware.Auth.Tests/TestUser.cs
Normal file
8
src/Robware.Auth.Tests/TestUser.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Robware.Auth.Tests {
|
||||
internal class TestUser : User {
|
||||
public TestUser(string username, string password) {
|
||||
Username = username;
|
||||
Password = password;
|
||||
}
|
||||
}
|
||||
}
|
31
src/Robware.Auth/Authenticator.cs
Normal file
31
src/Robware.Auth/Authenticator.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Robware.Auth {
|
||||
public class Authenticator : IAuthenticator {
|
||||
private readonly IUsers _users;
|
||||
private readonly ICryptographyProvider _crypto;
|
||||
|
||||
public Authenticator(IUsers users, ICryptographyProvider crypto) {
|
||||
_users = users;
|
||||
_crypto = crypto;
|
||||
}
|
||||
|
||||
public async Task<(AuthenticationResult Result, User User)> Authenticate(string username, string password) {
|
||||
try {
|
||||
var user = await _users.GetByEmail(username);
|
||||
return _crypto.Encrypt(password) == user.Password ? (AuthenticationResult.Success, user) : (AuthenticationResult.IncorrectPassword, null);
|
||||
}
|
||||
catch (UserNotFoundException) {
|
||||
return (AuthenticationResult.NotFound, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum AuthenticationResult {
|
||||
Unknown,
|
||||
Success,
|
||||
NotFound,
|
||||
IncorrectPassword
|
||||
}
|
||||
}
|
19
src/Robware.Auth/CryptographyProvider.cs
Normal file
19
src/Robware.Auth/CryptographyProvider.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Robware.Auth {
|
||||
public class CryptographyProvider : ICryptographyProvider {
|
||||
public string Encrypt(string input) {
|
||||
using (var sha256 = SHA256.Create()) {
|
||||
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
|
||||
|
||||
var builder = new StringBuilder();
|
||||
foreach (var b in hash)
|
||||
builder.Append(b.ToString("x2"));
|
||||
var hashString = builder.ToString();
|
||||
|
||||
return hashString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
src/Robware.Auth/IAuthenticator.cs
Normal file
7
src/Robware.Auth/IAuthenticator.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Robware.Auth {
|
||||
public interface IAuthenticator {
|
||||
Task<(AuthenticationResult Result, User User)> Authenticate(string username, string password);
|
||||
}
|
||||
}
|
5
src/Robware.Auth/ICryptographyProvider.cs
Normal file
5
src/Robware.Auth/ICryptographyProvider.cs
Normal file
|
@ -0,0 +1,5 @@
|
|||
namespace Robware.Auth {
|
||||
public interface ICryptographyProvider {
|
||||
string Encrypt(string input);
|
||||
}
|
||||
}
|
7
src/Robware.Auth/IUsers.cs
Normal file
7
src/Robware.Auth/IUsers.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Robware.Auth {
|
||||
public interface IUsers {
|
||||
Task<User> GetByEmail(string email);
|
||||
}
|
||||
}
|
7
src/Robware.Auth/Robware.Auth.csproj
Normal file
7
src/Robware.Auth/Robware.Auth.csproj
Normal file
|
@ -0,0 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
6
src/Robware.Auth/User.cs
Normal file
6
src/Robware.Auth/User.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Robware.Auth {
|
||||
public class User {
|
||||
public string Username { get; protected set; }
|
||||
public string Password { get; protected set; }
|
||||
}
|
||||
}
|
9
src/Robware.Auth/UserNotFoundException.cs
Normal file
9
src/Robware.Auth/UserNotFoundException.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace Robware.Auth {
|
||||
public class UserNotFoundException : Exception {
|
||||
public UserNotFoundException(string username) : base("Could not find user " + username) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
11
src/Robware.Data/DatabaseUser.cs
Normal file
11
src/Robware.Data/DatabaseUser.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using Robware.Auth;
|
||||
using Robware.Data.States;
|
||||
|
||||
namespace Robware.Data {
|
||||
public class DatabaseUser : User {
|
||||
public DatabaseUser(UserState state) {
|
||||
Username = state.User_Email;
|
||||
Password = state.User_Password;
|
||||
}
|
||||
}
|
||||
}
|
7
src/Robware.Data/IDatabaseProvider.cs
Normal file
7
src/Robware.Data/IDatabaseProvider.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
using System.Data;
|
||||
|
||||
namespace Robware.Data {
|
||||
public interface IDatabaseProvider {
|
||||
IDbConnection NewConnection();
|
||||
}
|
||||
}
|
12
src/Robware.Data/MySQLDatabaseProvider.cs
Normal file
12
src/Robware.Data/MySQLDatabaseProvider.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System.Data;
|
||||
using MySql.Data.MySqlClient;
|
||||
|
||||
namespace Robware.Data {
|
||||
public class MySQLDatabaseProvider : IDatabaseProvider {
|
||||
private readonly string _connectionString;
|
||||
|
||||
public MySQLDatabaseProvider(string connectionString) => _connectionString = connectionString;
|
||||
|
||||
public IDbConnection NewConnection() => new MySqlConnection(_connectionString);
|
||||
}
|
||||
}
|
16
src/Robware.Data/Robware.Data.csproj
Normal file
16
src/Robware.Data/Robware.Data.csproj
Normal file
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.0.35" />
|
||||
<PackageReference Include="MySqlConnector" Version="0.63.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Robware.Auth\Robware.Auth.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
10
src/Robware.Data/States/UserState.cs
Normal file
10
src/Robware.Data/States/UserState.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Robware.Data.States {
|
||||
public class UserState {
|
||||
public string User_Id { get; set; }
|
||||
public string User_Email { get; set; }
|
||||
public string User_Password { get; set; }
|
||||
public string User_Created { get; set; }
|
||||
public string User_Deleted { get; set; }
|
||||
public string Group_Id { get; set; }
|
||||
}
|
||||
}
|
29
src/Robware.Data/UserRepository.cs
Normal file
29
src/Robware.Data/UserRepository.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper;
|
||||
using Robware.Auth;
|
||||
using Robware.Data.States;
|
||||
|
||||
namespace Robware.Data {
|
||||
public class UserRepository : IUsers {
|
||||
private readonly IDatabaseProvider _dbProvider;
|
||||
|
||||
public UserRepository(IDatabaseProvider dbProvider) {
|
||||
_dbProvider = dbProvider;
|
||||
}
|
||||
|
||||
public async Task<User> GetByEmail(string email) {
|
||||
const string query = "SELECT * FROM users WHERE user_email=@email";
|
||||
|
||||
using (var connection = _dbProvider.NewConnection()) {
|
||||
connection.Open();
|
||||
var result = await connection.QueryAsync<UserState>(query, new { email });
|
||||
|
||||
if (!result.Any())
|
||||
throw new UserNotFoundException(email);
|
||||
|
||||
return new DatabaseUser(result.Single());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue