Skip to content

Commit

Permalink
Merge branch 'master' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
calebkleveter authored Jan 2, 2019
2 parents a5810c7 + d288946 commit dda9e34
Show file tree
Hide file tree
Showing 7 changed files with 37 additions and 22 deletions.
10 changes: 8 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM swift:4.1
FROM swift:4.2

ARG ENVIRONMENT
ENV ENVIRONMENT ${ENVIRONMENT:-production}
Expand All @@ -19,6 +19,12 @@ WORKDIR /root/vapor
RUN cd /root/vapor && rm -rf .build
RUN swift package update
RUN swift build --configuration release
EXPOSE 8080
#EXPOSE 8080
RUN export JWT_SECRET='ohFcON5JWImUWAa-SCC2yYOsFwlHwj3ZBOqpDNFX6JbOqkGrSaGjQWkieAj1fJhuYpTQq7A__s0G6yujmnE6N-I9UHEqXmKxI87ek9z5uxhzIeIHBS6ToyoXHECMS_jN8MbsM4bjec7FLuO9bVNJALFmCgEwcSzZdP9zFHjlj32ATWuSwXbNHNAJnk2IUk2eYiMNiG1BzZM8OApsCF1ASa9zcXdm2QYtOat7hhP-Uo6y_zflx9Ahg-CUBqPTpfOUUuJoGjeWgbhy0-ISveueGjzj7x5UYKNCRZyCircJ_-v51wFvx1lbgRmqH4eJy0dh8Ra-zmzLsFCDs2Akz8Oy0Q'
RUN export DATABASE_HOSTNAME='users.cpzpcvtsi0py.us-east-1.rds.amazonaws.com'
RUN export DATABASE_USER='users'
RUN export DATABASE_PASSWORD='k3AjY.eHcPVWxWM'
RUN export DATABASE_DB='users'
RUN export USER_JWT_D='IiLd9ex8LnXsFQ52jeK2HYPqf3-o6bT1PR_gM570kT0SkrH6TiwJowFuDTJ14qSIu6L0wPUCxbyRtH8gmqs2xAaXO5Zagj7vaMduAl8NCud_eKePKvxAhKGc9Ip0ApyJZCnCHqhOyZ1P0yyM_bYJLmgvQfQ2K-ByfT5BExLT54EFwUJ63tPQiU0gyycDULZAGTQBPzJNB5yWrVFW6s_VPZo73wd_4r86VErMeMgT0u4Nb5FihOcCjsdHt8X43oU4sf-YnHdzO7reHS8g11JLHrWL_sQlrC-gtJFq88UTzsevdsziDTByuB-Kf8cPATXPhTaisEb-TuURR_61wGLbQQ'
CMD .build/release/Run --hostname=0.0.0.0 --port=8080

10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ The names of the environment variables are the same ones used by Vapor Cloud, so

### JWT

You will also need to create an environment variable named `JWT_SECRET` with the `n` value of the JWK to verify access tokens. This service also signs the access tokens, so you will need another environment variable (called `USER_JWT_D` by default, but that can be changed) that contains the `d` value of the JWK.
You will also need to create an environment variable named `JWT_PUBLIC` with the `n` value of the JWK to verify access tokens. This service also signs the access tokens, so you will need another environment variable (called `JWT_SECRET` by default, but that can be changed) that contains the `d` value of the JWK.

### Email

Expand Down Expand Up @@ -132,22 +132,22 @@ The easiest way to start the service is by using the Dockerfile. The following c
```bash
# Note that you still need to setup the database before this step.
docker build . -t users
docker run -e JWT_SECRET='n-value-from-jwt' \
docker run -e JWT_PUBLIC='n-value-from-jwt' \
-e DATABASE_HOSTNAME='localhost' \
-e DATABASE_USER='users_service' \
-e DATABASE_PASSWORD='users_service' \
-e DATABASE_DB='users_service' \
-e USER_JWT_D='d-value-from-jwt' -p 8080:8080 users
-e JWT_SECRET='d-value-from-jwt' -p 8080:8080 users
```

If you want to run the server without docker the following summary of ENV variables that are needed may be helpful:
```bash
export JWT_SECRET='n-value-from-jwt'
export JWT_PUBLIC='n-value-from-jwt'
export DATABASE_HOSTNAME='localhost'
export DATABASE_USER='users_service'
export DATABASE_PASSWORD='users_service'
export DATABASE_DB='users_service'
export USER_JWT_D='d-value-from-jwt'
export JWT_SECRET='d-value-from-jwt'
```

## More Information about JWT and JWKS
Expand Down
8 changes: 5 additions & 3 deletions Sources/App/Configuration/configure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ public func configure(
try services.register(jwtProvider)

/// Register routes to the router
let router = EngineRouter.default()
try routes(router)
services.register(router, as: Router.self)
services.register(Router.self) { container -> EngineRouter in
let router = EngineRouter.default()
try routes(router, container)
return router
}

/// Register middleware
var middlewares = MiddlewareConfig() // Create _empty_ middleware config
Expand Down
7 changes: 5 additions & 2 deletions Sources/App/Configuration/router.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import Routing
import Vapor
import JWTMiddleware

/// Register your application's routes here.
///
/// [Learn More →](https://docs.vapor.codes/3.0/getting-started/structure/#routesswift)
public func routes(_ router: Router) throws {
public func routes(_ router: Router, _ container: Container) throws {

// Create a 'health' route useed by AWS to check if the server needs a re-boot.
router.get(any, "users", "health") { _ in
return "all good"
}

try router.register(collection: AuthController())
let jwtService = try container.make(JWTService.self)

try router.register(collection: AuthController(jwtService: jwtService))
try router.register(collection: VersionedCollection())
}
22 changes: 12 additions & 10 deletions Sources/App/Controllers/AuthController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import JWT

/// A route controller that handles user authentication with JWT.
final class AuthController: RouteCollection {
private let jwtService:JWTService

init(jwtService: JWTService) {
self.jwtService = jwtService
}

func boot(router: Router) throws {

let auth = router.grouped(any, "users")
Expand Down Expand Up @@ -131,13 +137,11 @@ final class AuthController: RouteCollection {

/// A route handler that returns a new access and refresh token for the user.
func refreshAccessToken(_ request: Request)throws -> Future<[String: String]> {
let signer = try request.make(JWTService.self)

// Get refresh token from request body and verify it.
let refreshToken = try request.content.syncGet(String.self, at: "refreshToken")
let refreshJWT = try JWT<RefreshToken>(from: refreshToken, verifiedUsing: signer.signer)
try refreshJWT.payload.verify(using: signer.signer)

// Get the user with the ID that was just fetched.
let userID = refreshJWT.payload.id
let user = User.find(userID, on: request).unwrap(or: Abort(.badRequest, reason: "No user found with ID '\(userID)'."))
Expand All @@ -148,12 +152,12 @@ final class AuthController: RouteCollection {

// Construct the new access token payload
let payload = try App.Payload(user: user)
return try request.payloadData(signer.sign(payload), with: ["userId": "\(user.requireID())"], as: JSON.self).and(result: payload)
return try request.payloadData(self.jwtService.sign(payload), with: ["userId": "\(user.requireID())"], as: JSON.self).and(result: payload)
}.map(to: [String: String].self) { payloadData in
let payload = try payloadData.0.merge(payloadData.1.json())

// Return the signed token with a success status.
let token = try signer.sign(payload)
let token = try self.jwtService.sign(payload)
return ["status": "success", "accessToken": token]
}
}
Expand All @@ -179,15 +183,13 @@ final class AuthController: RouteCollection {
/// The actual authentication is handled by the `JWTAuthenticatableMiddleware`.
/// The request's body should contain an email and a password for authenticating.
func login(_ request: Request)throws -> Future<LoginResponse> {
let signer = try request.make(JWTService.self)

let user = try request.requireAuthenticated(User.self)
let userPayload = try Payload(user: user)

// Create a payload using the standard data
// and the data from the registered `DataService`s
let remotePayload = try request.payloadData(
signer.sign(userPayload),
self.jwtService.sign(userPayload),
with: ["userId": "\(user.requireID())"],
as: JSON.self
)
Expand All @@ -196,8 +198,8 @@ final class AuthController: RouteCollection {
return remotePayload.map(to: LoginResponse.self) { remotePayload in
let payload = try remotePayload.merge(userPayload.json())

let accessToken = try signer.sign(payload)
let refreshToken = try signer.sign(RefreshToken(user: user))
let accessToken = try self.jwtService.sign(payload)
let refreshToken = try self.jwtService.sign(RefreshToken(user: user))

guard user.confirmed else { throw Abort(.badRequest, reason: "User not activated.") }

Expand Down
1 change: 1 addition & 0 deletions Sources/App/Models/Attribute/Attribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Vapor

/// An attribute for a `User` to store custom data..
final class Attribute: Content, MySQLModel, Migration, Parameter {
static let entity: String = "attributes"

/// The database ID of a class instance.
var id: Int?
Expand Down
1 change: 1 addition & 0 deletions Sources/App/Models/User/User.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Vapor

/// A generic user that can be conected to any service that uses JWT for authentication.
final class User: Content, MySQLModel, Migration, Parameter {
static let entity: String = "users"

/// The database ID of the class instance.
var id: Int?
Expand Down

0 comments on commit dda9e34

Please sign in to comment.