Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
rafiki270 committed Mar 26, 2019
1 parent c760b44 commit dcbf455
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 143 deletions.
6 changes: 6 additions & 0 deletions Sources/ApiCore/ApiCoreBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public class ApiCoreBase {
/// Databse config
public static var databaseConfig: DatabasesConfig?

/// Blocks of code executed when new user registers
public static var userDidRegister: [(User) -> ()] = []

/// Blocks of code executed when new user tries to register
public static var userShouldRegister: [(User) -> (Bool)] = []

/// Add / register model
public static func add<Model>(model: Model.Type, database: DatabaseIdentifier<Model.Database>) where Model: Fluent.Migration, Model: Fluent.Model, Model.Database: SchemaSupporting & QuerySupporting {
models.append(model)
Expand Down
145 changes: 2 additions & 143 deletions Sources/ApiCore/Controllers/UsersController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public class UsersController: Controller {
guard ApiCoreBase.configuration.auth.allowRegistrations == true else {
throw Error.registrationsNotPermitted
}
return try register(req)
return try UsersManager.register(req)
}

// Me
Expand Down Expand Up @@ -193,7 +193,7 @@ public class UsersController: Controller {
guard ApiCoreBase.configuration.auth.allowInvitations == true else {
throw Error.invitationsNotPermitted
}
return try invite(req)
return try UsersManager.invite(req)
}

// Input invitation data
Expand Down Expand Up @@ -299,144 +299,3 @@ public class UsersController: Controller {
}

}

// MARK: Common registration helpers

extension UsersController {

private static func checkDomain(email: String, for allowedDomains: [String]) throws {
if !allowedDomains.isEmpty {
guard let domain = email.domainFromEmail(), !domain.isEmpty else {
throw Error.domainNotAllowedForRegistration
}
guard allowedDomains.contains(domain) else {
throw Error.domainNotAllowedForRegistration
}
}
}

private static func checkExistingUser(email: String, on req: Request) -> Future<User?> {
return User.query(on: req).filter(\User.email == email).first().map(to: User?.self) { existingUser in
guard let existingUser = existingUser else {
return nil
}
return existingUser
}
}

private static func save(_ user: User, targetUri: String?, isInvite: Bool, on req: Request) throws -> Future<User> {
return user.save(on: req).flatMap(to: User.self) { user in
let jwtService = try req.make(JWTService.self)
let jwtToken = try jwtService.signEmailConfirmation(
user: user,
type: (isInvite ? .invitation : .registration),
redirectUri: targetUri,
on: req
)

// TODO: Add base64 encoded server image to the template!!!
let templateModel = try User.EmailTemplate(
verification: jwtToken,
link: req.serverURL().absoluteString.finished(with: "/") + "users/\(isInvite ? "input-invite" : "verify")?token=" + jwtToken,
user: user,
sender: isInvite ? req.me.user() : nil
)

let templateType: EmailTemplate.Type = isInvite ? InvitationEmailTemplate.self : RegistrationEmailTemplate.self

return try templateType.parsed(
model: templateModel,
on: req
).flatMap(to: User.self) { double in
let from = ApiCoreBase.configuration.mail.email
let subject = isInvite ? "Invitation" : "Registration" // TODO: Localize!!!!!!
let mail = Mailer.Message(from: from, to: user.email, subject: subject, text: double.string, html: double.html)
return try req.mail.send(mail).map(to: User.self) { mailResult in
switch mailResult {
case .success:
return user
default:
throw AuthError.emailFailedToSend
}
}
}
}
}

private static func invite(_ req: Request) throws -> Future<Response> {
return try User.Auth.EmailConfirmation.fill(post: req).flatMap(to: Response.self) { emailConfirmation in
if !ApiCoreBase.configuration.auth.allowedDomainsForInvitations.isEmpty {
guard ApiCoreBase.configuration.auth.allowInvitations == true else {
throw Error.invitationsNotPermitted
}
}
try checkDomain(
email: emailConfirmation.email,
for: ApiCoreBase.configuration.auth.allowedDomainsForInvitations
) // Check if domain is allowed in the system

return try User.Invitation.fill(post: req).flatMap(to: Response.self) { data in
return checkExistingUser(email: data.email, on: req).flatMap(to: Response.self) { existingUser in
let user: User
if let existingUser = existingUser {
if existingUser.verified == true {
// QUESTION: Do we want a more specific error? In this case no need to re-send invite as user is already registered
throw AuthError.emailExists
} else {
user = existingUser
}
} else {
user = try data.newUser(on: req)
}

if ApiCoreBase.configuration.general.singleTeam == true { // Single team scenario
return Team.adminTeam(on: req).flatMap(to: Response.self) { singleTeam in
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: true, on: req).flatMap(to: Response.self) { newUser in
return singleTeam.users.attach(newUser, on: req).flatMap(to: Response.self) { _ in
return try newUser.asDisplay().asResponse(.created, to: req)
}
}
}
} else {
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: true, on: req).flatMap(to: Response.self) { user in
return try user.asDisplay().asResponse(.created, to: req)
}
}
}
}
}
}

private static func register(_ req: Request) throws -> Future<Response> {
return try User.Auth.EmailConfirmation.fill(post: req).flatMap(to: Response.self) { emailConfirmation in
try checkDomain(
email: emailConfirmation.email,
for: ApiCoreBase.configuration.auth.allowedDomainsForRegistration
) // Check if domain is allowed in the system

return try User.Registration.fill(post: req).flatMap(to: Response.self) { data in
return checkExistingUser(email: data.email, on: req).flatMap(to: Response.self) { user in
guard user == nil else {
throw AuthError.emailExists
}
let user = try data.newUser(on: req)

if ApiCoreBase.configuration.general.singleTeam == true { // Single team scenario
return Team.adminTeam(on: req).flatMap(to: Response.self) { singleTeam in
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: false, on: req).flatMap(to: Response.self) { newUser in
return singleTeam.users.attach(newUser, on: req).flatMap(to: Response.self) { _ in
return try newUser.asDisplay().asResponse(.created, to: req)
}
}
}
} else {
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: false, on: req).flatMap(to: Response.self) { user in
return try user.asDisplay().asResponse(.created, to: req)
}
}
}
}
}
}

}
137 changes: 137 additions & 0 deletions Sources/ApiCore/Managers/UsersManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import Foundation
import Vapor
import FluentPostgreSQL
import ErrorsCore
import MailCore


public class UsersManager {
Expand All @@ -21,4 +23,139 @@ public class UsersManager {
}
}

public static func checkDomain(email: String, for allowedDomains: [String]) throws {
if !allowedDomains.isEmpty {
guard let domain = email.domainFromEmail(), !domain.isEmpty else {
throw UsersController.Error.domainNotAllowedForRegistration
}
guard allowedDomains.contains(domain) else {
throw UsersController.Error.domainNotAllowedForRegistration
}
}
}

public static func checkExistingUser(email: String, on req: Request) -> Future<User?> {
return User.query(on: req).filter(\User.email == email).first().map(to: User?.self) { existingUser in
guard let existingUser = existingUser else {
return nil
}
return existingUser
}
}

public static func save(_ user: User, targetUri: String?, isInvite: Bool, on req: Request) throws -> Future<User> {
return user.save(on: req).flatMap(to: User.self) { user in
let jwtService = try req.make(JWTService.self)
let jwtToken = try jwtService.signEmailConfirmation(
user: user,
type: (isInvite ? .invitation : .registration),
redirectUri: targetUri,
on: req
)

// TODO: Add base64 encoded server image to the template!!!
let templateModel = try User.EmailTemplate(
verification: jwtToken,
link: req.serverURL().absoluteString.finished(with: "/") + "users/\(isInvite ? "input-invite" : "verify")?token=" + jwtToken,
user: user,
sender: isInvite ? req.me.user() : nil
)

let templateType: EmailTemplate.Type = isInvite ? InvitationEmailTemplate.self : RegistrationEmailTemplate.self

return try templateType.parsed(
model: templateModel,
on: req
).flatMap(to: User.self) { double in
let from = ApiCoreBase.configuration.mail.email
let subject = isInvite ? "Invitation" : "Registration" // TODO: Localize!!!!!!
let mail = Mailer.Message(from: from, to: user.email, subject: subject, text: double.string, html: double.html)
return try req.mail.send(mail).map(to: User.self) { mailResult in
switch mailResult {
case .success:
return user
default:
throw AuthError.emailFailedToSend
}
}
}
}
}

public static func invite(_ req: Request) throws -> Future<Response> {
return try User.Auth.EmailConfirmation.fill(post: req).flatMap(to: Response.self) { emailConfirmation in
if !ApiCoreBase.configuration.auth.allowedDomainsForInvitations.isEmpty {
guard ApiCoreBase.configuration.auth.allowInvitations == true else {
throw UsersController.Error.invitationsNotPermitted
}
}
try checkDomain(
email: emailConfirmation.email,
for: ApiCoreBase.configuration.auth.allowedDomainsForInvitations
) // Check if domain is allowed in the system

return try User.Invitation.fill(post: req).flatMap(to: Response.self) { data in
return checkExistingUser(email: data.email, on: req).flatMap(to: Response.self) { existingUser in
let user: User
if let existingUser = existingUser {
if existingUser.verified == true {
// QUESTION: Do we want a more specific error? In this case no need to re-send invite as user is already registered
throw AuthError.emailExists
} else {
user = existingUser
}
} else {
user = try data.newUser(on: req)
}

if ApiCoreBase.configuration.general.singleTeam == true { // Single team scenario
return Team.adminTeam(on: req).flatMap(to: Response.self) { singleTeam in
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: true, on: req).flatMap(to: Response.self) { newUser in
return singleTeam.users.attach(newUser, on: req).flatMap(to: Response.self) { _ in
return try newUser.asDisplay().asResponse(.created, to: req)
}
}
}
} else {
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: true, on: req).flatMap(to: Response.self) { user in
return try user.asDisplay().asResponse(.created, to: req)
}
}
}
}
}
}

public static func register(_ req: Request) throws -> Future<Response> {
return try User.Auth.EmailConfirmation.fill(post: req).flatMap(to: Response.self) { emailConfirmation in
try checkDomain(
email: emailConfirmation.email,
for: ApiCoreBase.configuration.auth.allowedDomainsForRegistration
) // Check if domain is allowed in the system

return try User.Registration.fill(post: req).flatMap(to: Response.self) { data in
return checkExistingUser(email: data.email, on: req).flatMap(to: Response.self) { user in
guard user == nil else {
throw AuthError.emailExists
}
let user = try data.newUser(on: req)

if ApiCoreBase.configuration.general.singleTeam == true { // Single team scenario
return Team.adminTeam(on: req).flatMap(to: Response.self) { singleTeam in
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: false, on: req).flatMap(to: Response.self) { newUser in
return singleTeam.users.attach(newUser, on: req).flatMap(to: Response.self) { _ in
return try newUser.asDisplay().asResponse(.created, to: req)
}
}
}
} else {
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: false, on: req).flatMap(to: Response.self) { user in
return try user.asDisplay().asResponse(.created, to: req)
}
}
}
}
}
}

}

0 comments on commit dcbf455

Please sign in to comment.