diff --git a/Dockerfile b/Dockerfile index a95d196..5da17f2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM swift:4.1 +FROM swift:4.2 ARG ENVIRONMENT ENV ENVIRONMENT ${ENVIRONMENT:-production} @@ -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 diff --git a/README.md b/README.md index f03987c..c7e0f44 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 diff --git a/Sources/App/Configuration/configure.swift b/Sources/App/Configuration/configure.swift index 6628b97..e6c921e 100644 --- a/Sources/App/Configuration/configure.swift +++ b/Sources/App/Configuration/configure.swift @@ -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 diff --git a/Sources/App/Configuration/router.swift b/Sources/App/Configuration/router.swift index 732ca95..3c550e2 100644 --- a/Sources/App/Configuration/router.swift +++ b/Sources/App/Configuration/router.swift @@ -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()) } diff --git a/Sources/App/Controllers/AuthController.swift b/Sources/App/Controllers/AuthController.swift index 46a5e76..4a329d0 100644 --- a/Sources/App/Controllers/AuthController.swift +++ b/Sources/App/Controllers/AuthController.swift @@ -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") @@ -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(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)'.")) @@ -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] } } @@ -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 { - 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 ) @@ -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.") } diff --git a/Sources/App/Models/Attribute/Attribute.swift b/Sources/App/Models/Attribute/Attribute.swift index 6b4e15e..aeb5927 100644 --- a/Sources/App/Models/Attribute/Attribute.swift +++ b/Sources/App/Models/Attribute/Attribute.swift @@ -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? diff --git a/Sources/App/Models/User/User.swift b/Sources/App/Models/User/User.swift index 1d3a962..fefbebb 100644 --- a/Sources/App/Models/User/User.swift +++ b/Sources/App/Models/User/User.swift @@ -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?