Skip to content

Commit dcbf455

Browse files
committed
wip
1 parent c760b44 commit dcbf455

File tree

3 files changed

+145
-143
lines changed

3 files changed

+145
-143
lines changed

Sources/ApiCore/ApiCoreBase.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ public class ApiCoreBase {
4040
/// Databse config
4141
public static var databaseConfig: DatabasesConfig?
4242

43+
/// Blocks of code executed when new user registers
44+
public static var userDidRegister: [(User) -> ()] = []
45+
46+
/// Blocks of code executed when new user tries to register
47+
public static var userShouldRegister: [(User) -> (Bool)] = []
48+
4349
/// Add / register model
4450
public static func add<Model>(model: Model.Type, database: DatabaseIdentifier<Model.Database>) where Model: Fluent.Migration, Model: Fluent.Model, Model.Database: SchemaSupporting & QuerySupporting {
4551
models.append(model)

Sources/ApiCore/Controllers/UsersController.swift

Lines changed: 2 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public class UsersController: Controller {
153153
guard ApiCoreBase.configuration.auth.allowRegistrations == true else {
154154
throw Error.registrationsNotPermitted
155155
}
156-
return try register(req)
156+
return try UsersManager.register(req)
157157
}
158158

159159
// Me
@@ -193,7 +193,7 @@ public class UsersController: Controller {
193193
guard ApiCoreBase.configuration.auth.allowInvitations == true else {
194194
throw Error.invitationsNotPermitted
195195
}
196-
return try invite(req)
196+
return try UsersManager.invite(req)
197197
}
198198

199199
// Input invitation data
@@ -299,144 +299,3 @@ public class UsersController: Controller {
299299
}
300300

301301
}
302-
303-
// MARK: Common registration helpers
304-
305-
extension UsersController {
306-
307-
private static func checkDomain(email: String, for allowedDomains: [String]) throws {
308-
if !allowedDomains.isEmpty {
309-
guard let domain = email.domainFromEmail(), !domain.isEmpty else {
310-
throw Error.domainNotAllowedForRegistration
311-
}
312-
guard allowedDomains.contains(domain) else {
313-
throw Error.domainNotAllowedForRegistration
314-
}
315-
}
316-
}
317-
318-
private static func checkExistingUser(email: String, on req: Request) -> Future<User?> {
319-
return User.query(on: req).filter(\User.email == email).first().map(to: User?.self) { existingUser in
320-
guard let existingUser = existingUser else {
321-
return nil
322-
}
323-
return existingUser
324-
}
325-
}
326-
327-
private static func save(_ user: User, targetUri: String?, isInvite: Bool, on req: Request) throws -> Future<User> {
328-
return user.save(on: req).flatMap(to: User.self) { user in
329-
let jwtService = try req.make(JWTService.self)
330-
let jwtToken = try jwtService.signEmailConfirmation(
331-
user: user,
332-
type: (isInvite ? .invitation : .registration),
333-
redirectUri: targetUri,
334-
on: req
335-
)
336-
337-
// TODO: Add base64 encoded server image to the template!!!
338-
let templateModel = try User.EmailTemplate(
339-
verification: jwtToken,
340-
link: req.serverURL().absoluteString.finished(with: "/") + "users/\(isInvite ? "input-invite" : "verify")?token=" + jwtToken,
341-
user: user,
342-
sender: isInvite ? req.me.user() : nil
343-
)
344-
345-
let templateType: EmailTemplate.Type = isInvite ? InvitationEmailTemplate.self : RegistrationEmailTemplate.self
346-
347-
return try templateType.parsed(
348-
model: templateModel,
349-
on: req
350-
).flatMap(to: User.self) { double in
351-
let from = ApiCoreBase.configuration.mail.email
352-
let subject = isInvite ? "Invitation" : "Registration" // TODO: Localize!!!!!!
353-
let mail = Mailer.Message(from: from, to: user.email, subject: subject, text: double.string, html: double.html)
354-
return try req.mail.send(mail).map(to: User.self) { mailResult in
355-
switch mailResult {
356-
case .success:
357-
return user
358-
default:
359-
throw AuthError.emailFailedToSend
360-
}
361-
}
362-
}
363-
}
364-
}
365-
366-
private static func invite(_ req: Request) throws -> Future<Response> {
367-
return try User.Auth.EmailConfirmation.fill(post: req).flatMap(to: Response.self) { emailConfirmation in
368-
if !ApiCoreBase.configuration.auth.allowedDomainsForInvitations.isEmpty {
369-
guard ApiCoreBase.configuration.auth.allowInvitations == true else {
370-
throw Error.invitationsNotPermitted
371-
}
372-
}
373-
try checkDomain(
374-
email: emailConfirmation.email,
375-
for: ApiCoreBase.configuration.auth.allowedDomainsForInvitations
376-
) // Check if domain is allowed in the system
377-
378-
return try User.Invitation.fill(post: req).flatMap(to: Response.self) { data in
379-
return checkExistingUser(email: data.email, on: req).flatMap(to: Response.self) { existingUser in
380-
let user: User
381-
if let existingUser = existingUser {
382-
if existingUser.verified == true {
383-
// QUESTION: Do we want a more specific error? In this case no need to re-send invite as user is already registered
384-
throw AuthError.emailExists
385-
} else {
386-
user = existingUser
387-
}
388-
} else {
389-
user = try data.newUser(on: req)
390-
}
391-
392-
if ApiCoreBase.configuration.general.singleTeam == true { // Single team scenario
393-
return Team.adminTeam(on: req).flatMap(to: Response.self) { singleTeam in
394-
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: true, on: req).flatMap(to: Response.self) { newUser in
395-
return singleTeam.users.attach(newUser, on: req).flatMap(to: Response.self) { _ in
396-
return try newUser.asDisplay().asResponse(.created, to: req)
397-
}
398-
}
399-
}
400-
} else {
401-
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: true, on: req).flatMap(to: Response.self) { user in
402-
return try user.asDisplay().asResponse(.created, to: req)
403-
}
404-
}
405-
}
406-
}
407-
}
408-
}
409-
410-
private static func register(_ req: Request) throws -> Future<Response> {
411-
return try User.Auth.EmailConfirmation.fill(post: req).flatMap(to: Response.self) { emailConfirmation in
412-
try checkDomain(
413-
email: emailConfirmation.email,
414-
for: ApiCoreBase.configuration.auth.allowedDomainsForRegistration
415-
) // Check if domain is allowed in the system
416-
417-
return try User.Registration.fill(post: req).flatMap(to: Response.self) { data in
418-
return checkExistingUser(email: data.email, on: req).flatMap(to: Response.self) { user in
419-
guard user == nil else {
420-
throw AuthError.emailExists
421-
}
422-
let user = try data.newUser(on: req)
423-
424-
if ApiCoreBase.configuration.general.singleTeam == true { // Single team scenario
425-
return Team.adminTeam(on: req).flatMap(to: Response.self) { singleTeam in
426-
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: false, on: req).flatMap(to: Response.self) { newUser in
427-
return singleTeam.users.attach(newUser, on: req).flatMap(to: Response.self) { _ in
428-
return try newUser.asDisplay().asResponse(.created, to: req)
429-
}
430-
}
431-
}
432-
} else {
433-
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: false, on: req).flatMap(to: Response.self) { user in
434-
return try user.asDisplay().asResponse(.created, to: req)
435-
}
436-
}
437-
}
438-
}
439-
}
440-
}
441-
442-
}

Sources/ApiCore/Managers/UsersManager.swift

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import Foundation
99
import Vapor
1010
import FluentPostgreSQL
11+
import ErrorsCore
12+
import MailCore
1113

1214

1315
public class UsersManager {
@@ -21,4 +23,139 @@ public class UsersManager {
2123
}
2224
}
2325

26+
public static func checkDomain(email: String, for allowedDomains: [String]) throws {
27+
if !allowedDomains.isEmpty {
28+
guard let domain = email.domainFromEmail(), !domain.isEmpty else {
29+
throw UsersController.Error.domainNotAllowedForRegistration
30+
}
31+
guard allowedDomains.contains(domain) else {
32+
throw UsersController.Error.domainNotAllowedForRegistration
33+
}
34+
}
35+
}
36+
37+
public static func checkExistingUser(email: String, on req: Request) -> Future<User?> {
38+
return User.query(on: req).filter(\User.email == email).first().map(to: User?.self) { existingUser in
39+
guard let existingUser = existingUser else {
40+
return nil
41+
}
42+
return existingUser
43+
}
44+
}
45+
46+
public static func save(_ user: User, targetUri: String?, isInvite: Bool, on req: Request) throws -> Future<User> {
47+
return user.save(on: req).flatMap(to: User.self) { user in
48+
let jwtService = try req.make(JWTService.self)
49+
let jwtToken = try jwtService.signEmailConfirmation(
50+
user: user,
51+
type: (isInvite ? .invitation : .registration),
52+
redirectUri: targetUri,
53+
on: req
54+
)
55+
56+
// TODO: Add base64 encoded server image to the template!!!
57+
let templateModel = try User.EmailTemplate(
58+
verification: jwtToken,
59+
link: req.serverURL().absoluteString.finished(with: "/") + "users/\(isInvite ? "input-invite" : "verify")?token=" + jwtToken,
60+
user: user,
61+
sender: isInvite ? req.me.user() : nil
62+
)
63+
64+
let templateType: EmailTemplate.Type = isInvite ? InvitationEmailTemplate.self : RegistrationEmailTemplate.self
65+
66+
return try templateType.parsed(
67+
model: templateModel,
68+
on: req
69+
).flatMap(to: User.self) { double in
70+
let from = ApiCoreBase.configuration.mail.email
71+
let subject = isInvite ? "Invitation" : "Registration" // TODO: Localize!!!!!!
72+
let mail = Mailer.Message(from: from, to: user.email, subject: subject, text: double.string, html: double.html)
73+
return try req.mail.send(mail).map(to: User.self) { mailResult in
74+
switch mailResult {
75+
case .success:
76+
return user
77+
default:
78+
throw AuthError.emailFailedToSend
79+
}
80+
}
81+
}
82+
}
83+
}
84+
85+
public static func invite(_ req: Request) throws -> Future<Response> {
86+
return try User.Auth.EmailConfirmation.fill(post: req).flatMap(to: Response.self) { emailConfirmation in
87+
if !ApiCoreBase.configuration.auth.allowedDomainsForInvitations.isEmpty {
88+
guard ApiCoreBase.configuration.auth.allowInvitations == true else {
89+
throw UsersController.Error.invitationsNotPermitted
90+
}
91+
}
92+
try checkDomain(
93+
email: emailConfirmation.email,
94+
for: ApiCoreBase.configuration.auth.allowedDomainsForInvitations
95+
) // Check if domain is allowed in the system
96+
97+
return try User.Invitation.fill(post: req).flatMap(to: Response.self) { data in
98+
return checkExistingUser(email: data.email, on: req).flatMap(to: Response.self) { existingUser in
99+
let user: User
100+
if let existingUser = existingUser {
101+
if existingUser.verified == true {
102+
// QUESTION: Do we want a more specific error? In this case no need to re-send invite as user is already registered
103+
throw AuthError.emailExists
104+
} else {
105+
user = existingUser
106+
}
107+
} else {
108+
user = try data.newUser(on: req)
109+
}
110+
111+
if ApiCoreBase.configuration.general.singleTeam == true { // Single team scenario
112+
return Team.adminTeam(on: req).flatMap(to: Response.self) { singleTeam in
113+
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: true, on: req).flatMap(to: Response.self) { newUser in
114+
return singleTeam.users.attach(newUser, on: req).flatMap(to: Response.self) { _ in
115+
return try newUser.asDisplay().asResponse(.created, to: req)
116+
}
117+
}
118+
}
119+
} else {
120+
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: true, on: req).flatMap(to: Response.self) { user in
121+
return try user.asDisplay().asResponse(.created, to: req)
122+
}
123+
}
124+
}
125+
}
126+
}
127+
}
128+
129+
public static func register(_ req: Request) throws -> Future<Response> {
130+
return try User.Auth.EmailConfirmation.fill(post: req).flatMap(to: Response.self) { emailConfirmation in
131+
try checkDomain(
132+
email: emailConfirmation.email,
133+
for: ApiCoreBase.configuration.auth.allowedDomainsForRegistration
134+
) // Check if domain is allowed in the system
135+
136+
return try User.Registration.fill(post: req).flatMap(to: Response.self) { data in
137+
return checkExistingUser(email: data.email, on: req).flatMap(to: Response.self) { user in
138+
guard user == nil else {
139+
throw AuthError.emailExists
140+
}
141+
let user = try data.newUser(on: req)
142+
143+
if ApiCoreBase.configuration.general.singleTeam == true { // Single team scenario
144+
return Team.adminTeam(on: req).flatMap(to: Response.self) { singleTeam in
145+
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: false, on: req).flatMap(to: Response.self) { newUser in
146+
return singleTeam.users.attach(newUser, on: req).flatMap(to: Response.self) { _ in
147+
return try newUser.asDisplay().asResponse(.created, to: req)
148+
}
149+
}
150+
}
151+
} else {
152+
return try save(user, targetUri: emailConfirmation.targetUri, isInvite: false, on: req).flatMap(to: Response.self) { user in
153+
return try user.asDisplay().asResponse(.created, to: req)
154+
}
155+
}
156+
}
157+
}
158+
}
159+
}
160+
24161
}

0 commit comments

Comments
 (0)