Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Authentication to API #12

Merged
merged 76 commits into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
0393f2e
- Added starting points for user authentication and authorization
MikeDrewitt Jun 6, 2021
8e9a5ea
- SAML working w/ client
MikeDrewitt Jun 8, 2021
ca79be3
Provider default json created
MikeDrewitt Jun 8, 2021
4325775
- Auth working
MikeDrewitt Jun 9, 2021
3defe76
- Moving shared modueles out
MikeDrewitt Jun 9, 2021
1394545
Moved json back to env (couldn't change branches)
MikeDrewitt Jun 9, 2021
68d4d87
Update json configs
MikeDrewitt Jun 10, 2021
699e34a
[WIP] Implement additional JWT controls
daviddob Jun 10, 2021
acfc209
Updated login controller to support developer login correctly
MikeDrewitt Jun 10, 2021
b3f1ce0
Merged things together
MikeDrewitt Jun 10, 2021
ed78f81
First round cleanup
MikeDrewitt Jun 10, 2021
e14a896
Working through PR
MikeDrewitt Jun 11, 2021
32d1264
Fisrt draft PR
MikeDrewitt Jun 11, 2021
f1487d9
Updated ts settings
MikeDrewitt Jun 11, 2021
2d0f1dd
Nearly there
MikeDrewitt Jun 11, 2021
749691e
Rename user columns
MikeDrewitt Jun 11, 2021
b29ea7f
Broke out controllers and routers
MikeDrewitt Jun 11, 2021
440f3c7
No need 4 middleware
MikeDrewitt Jun 11, 2021
87ddda5
Implement SAML Metadata endpoint
daviddob Jun 11, 2021
68af998
[WIP] Add base config generator script
daviddob Jun 12, 2021
c531446
Initial genConfig dockerfile
daviddob Jun 12, 2021
dade184
Update image to alpine and add generate-config npm command
daviddob Jun 12, 2021
dcd37e3
- Added starting points for user authentication and authorization
MikeDrewitt Jun 6, 2021
05f5f7a
- SAML working w/ client
MikeDrewitt Jun 8, 2021
1fa712f
Provider default json created
MikeDrewitt Jun 8, 2021
9812125
- Auth working
MikeDrewitt Jun 9, 2021
f95d1d6
- Moving shared modueles out
MikeDrewitt Jun 9, 2021
8f10b46
Moved json back to env (couldn't change branches)
MikeDrewitt Jun 9, 2021
8279f47
Update json configs
MikeDrewitt Jun 10, 2021
f2916ce
[WIP] Implement additional JWT controls
daviddob Jun 10, 2021
a699087
Updated login controller to support developer login correctly
MikeDrewitt Jun 10, 2021
c48fa2f
Merged things together
MikeDrewitt Jun 10, 2021
cf24e0f
First round cleanup
MikeDrewitt Jun 10, 2021
eaf1dd3
Working through PR
MikeDrewitt Jun 11, 2021
21aac0d
Fisrt draft PR
MikeDrewitt Jun 11, 2021
efeea26
Updated ts settings
MikeDrewitt Jun 11, 2021
4c975e0
Nearly there
MikeDrewitt Jun 11, 2021
9fe5ab9
Rename user columns
MikeDrewitt Jun 11, 2021
f170da3
Broke out controllers and routers
MikeDrewitt Jun 11, 2021
406be7b
No need 4 middleware
MikeDrewitt Jun 11, 2021
17202a5
Implement SAML Metadata endpoint
daviddob Jun 11, 2021
4e8866c
[WIP] Add base config generator script
daviddob Jun 12, 2021
956d096
Initial genConfig dockerfile
daviddob Jun 12, 2021
c59dea7
Update image to alpine and add generate-config npm command
daviddob Jun 12, 2021
0d04680
Tested and fixed SAML auth after config refactor
daviddob Jun 12, 2021
6a67afa
Updaetd tests
MikeDrewitt Jun 12, 2021
ba4d32c
Generalized token verification
MikeDrewitt Jun 12, 2021
77e912f
Login controller tested
MikeDrewitt Jun 13, 2021
8af6aba
Including test stubbed config
MikeDrewitt Jun 13, 2021
104c75f
Login developer controller tests
MikeDrewitt Jun 13, 2021
926f7b8
Saml controller tests
MikeDrewitt Jun 13, 2021
7f08064
Authmiddleware tests
MikeDrewitt Jun 13, 2021
e79afac
Updated Readme
MikeDrewitt Jun 13, 2021
c01c041
Update gitignore
MikeDrewitt Jun 13, 2021
ad29543
Rename constraint names in migrations
MikeDrewitt Jun 13, 2021
a9d3dcc
Removed unused env vars
MikeDrewitt Jun 13, 2021
78c46cb
Use logging db from yaml
MikeDrewitt Jun 13, 2021
c8ef9b8
Force login user if their refresh token is within a buffer of it's ex…
MikeDrewitt Jun 13, 2021
179e7d8
Added tests for expiration buffer
MikeDrewitt Jun 13, 2021
be83a1d
Update some docs
MikeDrewitt Jun 13, 2021
4f7b14a
Updated package-lock.json
MikeDrewitt Jun 15, 2021
7c4cf47
Typo cleanups
jpobzy Jun 17, 2021
1c4081b
Merge remote-tracking branch 'origin/auth' into auth
jpobzy Jun 17, 2021
74b2279
Typo cleanups
jpobzy Jun 17, 2021
5738581
Fix in PR
MikeDrewitt Jun 21, 2021
b99938e
Apply suggestions from code review
MikeDrewitt Jun 21, 2021
7a0d3fa
Updated PR stuffs
MikeDrewitt Jun 21, 2021
5a7a31a
Working
MikeDrewitt Jun 22, 2021
e5dc747
Removed default.yml
MikeDrewitt Jun 22, 2021
48826b3
removed refresh token
MikeDrewitt Jun 22, 2021
5aaebdd
Add styleguide to readme
MikeDrewitt Jun 22, 2021
af6357a
PR mostly done
MikeDrewitt Jun 22, 2021
1835081
Remove dev auth
MikeDrewitt Jun 22, 2021
7ce6127
Add validation to passport provider callbacks
MikeDrewitt Jun 22, 2021
74cb0ed
Updated docs fixed saml bug
MikeDrewitt Jun 23, 2021
60672a1
Changed auth shared to master
MikeDrewitt Jun 23, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
node_modules/
build/
env/

config/*.yml
!config/test.yml
MikeDrewitt marked this conversation as resolved.
Show resolved Hide resolved

.DS_Store
6 changes: 6 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
"runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],

"args": ["src/index.ts"],
"env": {},

"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/**"
],

"cwd": "${workspaceRoot}",
"internalConsoleOptions": "openOnSessionStart",
Expand Down
8 changes: 4 additions & 4 deletions @types/express/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import User from '../../src/model/users.model'

import { AccessToken, RefreshToken } from 'devu-shared-modules'
declare global {
namespace Express {
interface Request {
user?: User
users?: User[]
// Auth Data
currentUser?: AccessToken // Deserialized access token
refreshUser?: RefreshToken // Deserialized refresh token
}
}
}
58 changes: 49 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,19 @@ Run the initial migrations to setup our DB schema
npm run typeorm -- migration:run
```

Run the setup script to create local development auth keys. These are used in local development for signing and authenticating JWTs.

```
npm run generate-config
```

MikeDrewitt marked this conversation as resolved.
Show resolved Hide resolved
Once you've got all the dependencies installed you can run the project via

```
npm start
```

By defualt the project runs at `localhost:3001`, but you can change the port by setting an alternate port by setting the `PORT` environment variable.
By default the project runs at `localhost:3001`, but you can change the port by setting an alternate port by setting the `PORT` environment variable.

If you're working in vscode, a configuration has been included to allow for debugging. Open up the VS Code Run and Debug section and click `Debug API`.

Expand All @@ -67,6 +73,10 @@ npm run format

Use [Azure Data Studio](https://docs.microsoft.com/en-us/sql/azure-data-studio/download-azure-data-studio?view=sql-server-ver15) (or some other sql client) for easier sql queries/ testing while developing. We're using [PostgresSQL](https://www.postgresql.org/) so keep that in mind while googling how to query things as the syntax and features can be slightly different across sql flavors. **[AZURE DATA STUDIO REQUIRES A POSTGRES PLUGIN TO WORK WITH POSTGRESQL](https://docs.microsoft.com/en-us/sql/azure-data-studio/extensions/postgres-extension?view=sql-server-ver15)**. So if you want to use Azure Data Studio, you'll have to install that extension.

### Local Auth Providers

If you're looking for more information on how to setup auth for your developement environment, check out more info [here](./docs/localAuth.md)

## Working in the Project

This project is built using
Expand All @@ -82,7 +92,7 @@ For those unfamiliar with Express, I'll attempt to give a brief rundown before t
Express is a REST framework for node. Devs new to express will likely find one of the most perplexing parts of Express to be how it handles middleware. For those unfamiliar with what middleware is, it's largely a catch all term for code that connects together pieces of code in the api. In Express's context, middleware is basically everything that runs within each router; here's an example:

```typescript
Router.get('/:id', idAsInt, UserController.detail, serializer)
Router.get('/:id', idAsInt, UserController.detail)
```

In express, all of the functions added after the route's path are considered middleware and each route can have as many middleware as is needed. In this project we separate out `controllers` into their own directory though they are still considered middleware.
Expand Down Expand Up @@ -113,21 +123,21 @@ Let's take this from the top
- `index.ts`: where the application is bootstrapped from, controls all global server controls and middlewares
- `routers/index.ts`: largely a rollup for all the other routers. Can be used to add router specific middleware to routes/ subroutes
- `routers/route.ts`: Individual routes for each resource, where the list of middleware can be found. _All routers call unique middleware_
- Middleware: The above diagram is a bit of a misnomer. Not every endpoint will have validators, controllers, and serializers. Some will have all of those, some may have none. Each route will have at least one middleware, and the last middleware will deal with returning the requested data
- Middleware: The above diagram is a bit of a misnomer. Not every endpoint will have auth, validators, and controllers. Some will have all of those, some may have none. Each route will have at least one middleware, and the last middleware will deal with returning the requested data
- Auth: checks the access/ refresh tokens
- Validators: validates the bodies of requests
- Controllers: deals with setting status codes, and directing to services
- Controllers: deals with setting status codes, and directing to services. For the most part, controllers should be the last piece of middleware in the chain.
- Services: Workhorse of the application. Deals with all major application logic and database calls
- Serializers: Formats the data to be a sane, reusable response.

The database models live outside of this control flow as they don't deal with any buisness logic. However services will use them to access the database. You can largely think of the the models as a 1:1 map to database tables.
The database models live outside of this control flow as they don't deal with any buisness logic. However services will use them to access the database. You can largely think of the models as a 1:1 map to database tables.

### Shared Modules

This project uses shared modules between it and it's client. What this means is that there exists code that operates in both this project and it's client. That project is being imported here as `devu-shared-modules`.

The project can be found [here](https://github.com/UBAutograding/devu-shared).

When developing if you need to update the modules, you can do so by updating the branch or SHA on the package url in the `package.json`. As an example, if you wanted to use shared modules from the `auth` branch, you could change the dependancy line in the `package.json` to:
When developing if you need to update the modules, you can do so by updating the branch or SHA on the package url in the `package.json`. As an example, if you wanted to use shared modules from the `auth` branch, you could change the dependency line in the `package.json` to:

```
"devu-shared-modules": "github:UBAutograding/devu-shared#auth",
Expand All @@ -141,7 +151,7 @@ npm install

to install the updated package

If you were to make changes to the your shared branch and push it to the remote, you can update what version you local files are looking at via
If you were to make changes to your shared branch and push it to the remote, you can update what version your local files are looking at via

```
npm update devu-shared-modules
Expand Down Expand Up @@ -175,7 +185,7 @@ And lastly, if you want to narrow it down to running less than a single test fil
npm test -- -t "some test description or name"
```

I wouldn't reccomend digging that far down as the of tests should be more human readable, and therefor not super great to grab via a regex; but if you really wish to do so, you can.
I wouldn't recommend digging that far down as the of tests should be more human-readable, and therefore not super great to grab via a regex; but if you really wish to do so, you can.

### Common TypeORM Commands (Database/ Schema Stuff)

Expand All @@ -197,6 +207,36 @@ And revert the latest migration with
npm run typeorm --migration:revert
```

### Configuration Options

Most of the API's configuraiton options live in a file called `environment.ts`. That file is bootstrapped at startup using the [config library]https://www.npmjs.com/package/config), as well as some environment variables.

What this means for you (the developer) is that you can control certain api options via environment variables at runtime, or by using your `config/default.yml`. To see which `environment.ts` options support using environment variables directly, open that file and see which are using `process.env.*`; everything else should be configurable via your `default.yml`

If you update your `default.yml` be sure to hard restart your API, as changing the config options does not update until you rebootstrap the API.

### Style Guide

While we use prettier to control most of the style choices for the application, it is not a silver bullet. Here we'll list a few conventions that will (hopefully) make working in the API a little bit more sane.

When importing stuff we try to keep things in a certain order. Example

```
import Libraries

import RootLevelStuff

import Controllers

import Middleware

import Routers

import Utils
```

The important thing here is once you get into the modules (models/ controllers/ routers/ etc), import them alphebetically with a single empty line inbetween them.

## Production Builds

You can build the project as a production build
Expand Down
78 changes: 78 additions & 0 deletions config/default.yml.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
api:
clientUrl: http://localhost:9000
scheme: http
host: localhost
port: 3001

database:
host: 'localhost'
username: 'typescript_user'
password: 'password'
name: 'typescript_api'

logging:
db: false

auth:
jwt:
activeKeyId: sk07112021
accessTokenValiditySeconds: 600 # 10 minutes (seconds)
refreshTokenValiditySeconds: 864000 # 10 days (seconds)
refreshTokenExpirationBufferSeconds: 86400 # 1 days (seconds)
keys:
sk07112021:
privateKey: |
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
publicKey: |
-----BEGIN RSA PUBLIC KEY-----
...
-----END RSA PUBLIC KEY-----

providers:
devAuth:
enabled: true

saml:
name: MyUB
enabled: false
entryPoint: https://samltest.id/idp/profile/SAML2/Redirect/SSO
acceptedClockSkewSeconds: 20
attributeMap:
urn:oid:0.9.2342.19200300.100.1.3: email
urn:oid:0.9.2342.19200300.100.1.1: externalId
urn:oasis:names:tc:SAML:attribute:subject-id: identifier
urn:oid:2.5.4.4: sn
urn:oid:2.16.840.1.113730.3.1.241: displayName
urn:oid:2.5.4.20: telephoneNumber
urn:oid:2.5.4.42: givenName
https://samltest.id/attributes/role: role
urn:oid:1.3.6.1.4.1.5923.1.1.1.7: eduPersonEntitlement
idpCerts:
- |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
- |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
encryption:
privateKey: |
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
certificate: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
signing:
privateKey: |
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
certificate: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
2 changes: 2 additions & 0 deletions config/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# If you need any env stuff for tests, place here.
# This file exists solely to prevent jest & config from throwing console errors
Binary file modified docs/controlFlow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 53 additions & 0 deletions docs/localAuth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Local Auth

## Enabling Developer Auth

By default (aka in the `default.yml.template` file) developer auth is enabled. If you don't care how that works you can just use the config as is and you should be good to go. (It'll look something like this)

```yaml
providers:
devAuth:
enabled: true
```

If you wish to better understand how this works, here's a bit more information.

When developer auth is enabled, users can provide the email & externalId of the user they wish to login as (or create) and recieve a valid refresh/ access token.

With this flag on, it enabled the `/login/developer` route (see `./router/login.router.ts` for more details).

## Testing SAML Authentication Locally

To test SAML authentication, you will need to configure an Identity Provider (IDP) for the API to authenticate against. As we don't expect anyone to just have a configured IDP laying around ready to go, this will walk through using [SamlTest.id](https://samltest.id/).

[SamlTest.id](https://samltest.id/) is an IDP specifically designed for testing. To use it in this project, we can configure it as follows.

1. If you haven't already, run `npm run generate-config` to generate a base config with keys.
1. Download the IDP metadata from https://samltest.id/saml/idp.
1. Open the downladed IDP metadata xml file and pull out the two `signing` keys.
1. Add these to your `default.yml` config under `providers.saml.idpCerts` between the begin and end certificate tags as stubbed out for you as follows.

```yaml
idpCerts:
- |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
- |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
```

1. Enable the SAML provider in the `default.yml` config.

```yaml
saml:
name: MySAMLName
enabled: true
```

1. At this point you should be all set to run the API for the first time, though SAML auth will _not_ work yet.
1. Go to `<api-url>/login/saml/metadata` and save the SP metadata file it presents you with.
1. Navigate to https://samltest.id/upload.php and upload the SP metadata from the previous step.
1. Now you should be all set for local SAML testing and can try logging in.
Loading