Skip to content

Commit 7d4aed3

Browse files
committed
Initial commit
0 parents  commit 7d4aed3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+2837
-0
lines changed

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
root = true
2+
3+
[*]
4+
end_of_line = lf
5+
charset = utf-8
6+
indent_style = space
7+
trim_trailing_whitespace = true
8+
insert_final_newline = true
9+
indent_size = 2
10+
11+
[*.{js,ts,tsx}]
12+
indent_size = 4

.gitignore

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
.DS_Store
2+
3+
# dotnet core
4+
bin/
5+
obj/
6+
api/wwwroot/**
7+
project.lock.json
8+
9+
# node
10+
node_modules/
11+
typings/
12+
npm-debug.log
13+
14+
# client-react
15+
/client-react/components/*.js
16+
/client-react.test/build/
17+
18+
*.js.map
19+
20+
# ops
21+
ops/hosts
22+
ops/*.retry

.vscode/launch.json

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Debug server",
6+
"type": "coreclr",
7+
"request": "launch",
8+
"preLaunchTask": "build",
9+
"program": "${workspaceRoot}/api/bin/Debug/netcoreapp1.0/server.dll",
10+
"args": [],
11+
"env": {
12+
"ASPNETCORE_ENVIRONMENT": "Development",
13+
"NODE_PATH": "../node_modules/"
14+
},
15+
"cwd": "${workspaceRoot}/api",
16+
"externalConsole": false,
17+
"stopAtEntry": false,
18+
"internalConsoleOptions": "openOnSessionStart"
19+
},
20+
{
21+
"name": "Debug server xUnit tests",
22+
"type": "coreclr",
23+
"request": "launch",
24+
"preLaunchTask": "build",
25+
"program": "/usr/local/share/dotnet/dotnet",
26+
"args": [
27+
"exec",
28+
"--runtimeconfig",
29+
"${workspaceRoot}/api.test/bin/Debug/netcoreapp1.0/api.test.runtimeconfig.json",
30+
"--depsfile",
31+
"${workspaceRoot}/api.test/bin/Debug/netcoreapp1.0/api.test.deps.json",
32+
"--additionalprobingpath",
33+
"/Users/bholt/.nuget/packages",
34+
"/Users/bholt/.nuget/packages/dotnet-test-xunit/1.0.0-rc2-build10015/lib/netcoreapp1.0/dotnet-test-xunit.dll",
35+
"${workspaceRoot}/api.test/bin/Debug/netcoreapp1.0/api.test.dll",
36+
"-namespace",
37+
"Tests"
38+
],
39+
"cwd": "${workspaceRoot}",
40+
"stopAtEntry": false
41+
},
42+
{
43+
"name": "Debug client-react Mocha tests",
44+
"type": "node",
45+
"request": "launch",
46+
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
47+
"stopOnEntry": false,
48+
"args": [
49+
"client-react.test/build/tests.js"
50+
],
51+
"cwd": "${workspaceRoot}",
52+
"preLaunchTask": "pretest:react",
53+
"runtimeArgs": [
54+
"--nolazy"
55+
],
56+
"env": {
57+
"NODE_ENV": "development"
58+
},
59+
"console": "internalConsole",
60+
"sourceMaps": true,
61+
"outDir": "${workspaceRoot}/client-react.test/build/"
62+
},
63+
{
64+
"name": ".NET Core Attach",
65+
"type": "coreclr",
66+
"request": "attach",
67+
"processId": "${command.pickProcess}"
68+
}
69+
]
70+
}

.vscode/tasks.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
// See https://go.microsoft.com/fwlink/?LinkId=733558
3+
// for the documentation about the tasks.json format
4+
"version": "0.1.0",
5+
"command": "npm",
6+
"isShellCommand": true,
7+
"showOutput": "always",
8+
"suppressTaskName": true,
9+
"tasks": [
10+
{
11+
"taskName": "build",
12+
"args": [
13+
"run",
14+
"build"
15+
],
16+
"isBuildCommand": true
17+
},
18+
{
19+
"taskName": "test",
20+
"args": [
21+
"run",
22+
"test"
23+
],
24+
"isTestCommand": true
25+
},
26+
{
27+
"taskName": "pretest:react",
28+
"args":[
29+
"run",
30+
"pretest:react"
31+
]
32+
}
33+
]
34+
}

README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# ASP.NET Core / React SPA Template App
2+
3+
This app is a template application using ASP.NET Core for a REST/JSON API server and React for a web client.
4+
5+
## Overview of Stack
6+
- Server
7+
- ASP.NET Core
8+
- PostgresSQL
9+
- Entity Framework Core w/ EF Migrations
10+
- JSON Web Token (JWT) authorization with OpenIddict
11+
- Client
12+
- React
13+
- Webpack for asset bundling and HMR
14+
- Testing
15+
- xUnit for .NET Core
16+
- Enzyme for React
17+
- DevOps
18+
- Ansible playbook for provisioning (Nginx reverse proxy, SSL via Let's Encrypt, PostgresSQL backups to S3)
19+
- Ansible playbook for deployment
20+
21+
## Setup
22+
23+
1. Install the following:
24+
- [.NET Core](https://www.microsoft.com/net/core)
25+
- [Node.js >= 4.0](https://nodejs.org/en/download/)
26+
- [Ansible >= 2.0](http://docs.ansible.com/ansible/intro_installation.html)
27+
- [Docker](https://docs.docker.com/engine/installation/)
28+
2. Run `npm install && npm start`
29+
3. Open browser and navigate to [http://localhost:5000](http://localhost:5000).
30+
31+
## Scripts
32+
33+
### `npm install`
34+
35+
When first cloning the repo or adding new dependencies, run this command. This will:
36+
37+
- Install Node dependencies from package.json
38+
- Install .NET Core dependencies from api/project.json and api.test/project.json (dotnet restore)
39+
40+
### `npm start`
41+
42+
To start the app for development, run this command. This will:
43+
44+
- Run `docker-compose up` to ensure the Postgres Docker image is up and running
45+
- Run dotnet watch run which will build the app (if changed), watch for changes and start the web server on http://localhost:5000
46+
- Run Webpack dev middleware with HMR via [ASP.NET JavaScriptServices](https://github.com/aspnet/JavaScriptServices)
47+
48+
### `npm test`
49+
50+
This will run the xUnit tests in api.test/ and the Mocha tests in client-react.test/.
51+
52+
### `npm run provision:prod`
53+
54+
_Before running this script, you need to create a ops/hosts file first. See the [ops README](ops/) for instructions._
55+
56+
This will run the ops/provision.yml Ansible playbook and provision hosts in ops/hosts inventory file. This prepares the hosts to recieve deployments by doing the following:
57+
- Install Nginx
58+
- Generate a SSL certificate from [Let's Encrypt](https://letsencrypt.org/) and configure Nginx to use it
59+
- Install .Net Core
60+
- Install Supervisor (will run/manage the ASP.NET app)
61+
- Install PostgreSQL
62+
- Setup a cron job to automatically backup the PostgresSQL database, compress it, and upload it to S3.
63+
- Setup UFW (firewall) to lock everything down except inbound SSH and web traffic
64+
- Create a deploy user, directory for deployments and configure Nginx to serve from this directory
65+
66+
### `npm run deploy:prod`
67+
68+
_Before running this script, you need to create a ops/hosts file first. See the [ops README](ops/) for instructions._
69+
70+
This script will:
71+
- Build release Webpack bundles
72+
- Package the .NET Core application in Release mode (dotnet publish)
73+
- Run the ops/deploy.yml Ansible playbook to deploy this app to hosts in /ops/hosts inventory file. This does the following:
74+
- Copies the build assets to the remote host(s)
75+
- Updates the `appsettings.release.json` file with PostgresSQL credentials specified in ops/hosts file and the app URL (needed for JWT tokens)
76+
- Restarts the app so that changes will be picked up

api.test/NuGet.Config

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<packageSources>
4+
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
5+
<add key="aspnet-contrib" value="https://www.myget.org/F/aspnet-contrib/api/v3/index.json" />
6+
</packageSources>
7+
</configuration>

api.test/Tests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
using Xunit;
3+
4+
namespace Tests
5+
{
6+
public class Tests
7+
{
8+
[Fact]
9+
public void Test1()
10+
{
11+
var contact = new vipper.Models.Contact();
12+
Assert.True(string.IsNullOrEmpty(contact.Email));
13+
}
14+
}
15+
}

api.test/project.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"version": "1.0.0-*",
3+
"buildOptions": {
4+
"debugType": "portable"
5+
},
6+
"dependencies": {
7+
"System.Runtime.Serialization.Primitives": "4.1.1",
8+
"xunit": "2.1.0",
9+
"dotnet-test-xunit": "1.0.0-rc2-192208-24",
10+
"api": {
11+
"target": "project"
12+
}
13+
},
14+
"testRunner": "xunit",
15+
"frameworks": {
16+
"netcoreapp1.0": {
17+
"dependencies": {
18+
"Microsoft.NETCore.App": {
19+
"type": "platform",
20+
"version": "1.0.0"
21+
}
22+
},
23+
"imports": [
24+
"dotnet5.4",
25+
"portable-net451+win8"
26+
]
27+
}
28+
}
29+
}

api/Controllers/AuthController.cs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System.Security.Claims;
2+
using System.Threading.Tasks;
3+
using AspNet.Security.OpenIdConnect.Extensions;
4+
using AspNet.Security.OpenIdConnect.Server;
5+
using Microsoft.AspNetCore.Authentication;
6+
using Microsoft.AspNetCore.Authorization;
7+
using Microsoft.AspNetCore.Builder;
8+
using Microsoft.AspNetCore.Http.Authentication;
9+
using Microsoft.AspNetCore.Identity;
10+
using Microsoft.AspNetCore.Mvc;
11+
using Microsoft.Extensions.Logging;
12+
using OpenIddict;
13+
using vipper.Models;
14+
15+
namespace vipper.Controllers
16+
{
17+
public class AuthController : Controller
18+
{
19+
private readonly OpenIddictUserManager<ApplicationUser> _userManager;
20+
private readonly ILogger _logger;
21+
22+
public AuthController(
23+
OpenIddictUserManager<ApplicationUser> userManager,
24+
ILoggerFactory loggerFactory)
25+
{
26+
_userManager = userManager;
27+
_logger = loggerFactory.CreateLogger<AuthController>();
28+
}
29+
30+
[AllowAnonymous]
31+
[HttpPost("~/api/auth/login")]
32+
[Produces("application/json")]
33+
public async Task<IActionResult> Login()
34+
{
35+
var request = HttpContext.GetOpenIdConnectRequest();
36+
37+
if (request.IsPasswordGrantType())
38+
{
39+
var user = await _userManager.FindByNameAsync(request.Username);
40+
if (user == null)
41+
{
42+
return BadRequest(new OpenIdConnectResponse
43+
{
44+
Error = OpenIdConnectConstants.Errors.InvalidGrant,
45+
ErrorDescription = "The username/password couple is invalid."
46+
});
47+
}
48+
49+
// Ensure the password is valid.
50+
if (!await _userManager.CheckPasswordAsync(user, request.Password))
51+
{
52+
return BadRequest(new OpenIdConnectResponse
53+
{
54+
Error = OpenIdConnectConstants.Errors.InvalidGrant,
55+
ErrorDescription = "The username/password couple is invalid."
56+
});
57+
}
58+
59+
var identity = await _userManager.CreateIdentityAsync(user, request.GetScopes());
60+
61+
// Add a custom claim that will be persisted
62+
// in both the access and the identity tokens.
63+
identity.AddClaim("given_name", user.GivenName,
64+
OpenIdConnectConstants.Destinations.AccessToken,
65+
OpenIdConnectConstants.Destinations.IdentityToken);
66+
67+
// Create a new authentication ticket holding the user identity.
68+
var ticket = new AuthenticationTicket(
69+
new ClaimsPrincipal(identity),
70+
new AuthenticationProperties(),
71+
OpenIdConnectServerDefaults.AuthenticationScheme);
72+
73+
ticket.SetResources(request.GetResources());
74+
ticket.SetScopes(request.GetScopes());
75+
76+
_logger.LogInformation(1, "User logged in.");
77+
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
78+
}
79+
80+
return BadRequest(new OpenIdConnectResponse
81+
{
82+
Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
83+
ErrorDescription = "The specified grant type is not supported."
84+
});
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)