1 /++
2   A module containing the configuration structures used to setup your auth process
3 
4   Copyright: © 2018 Szabo Bogdan
5   License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6   Authors: Szabo Bogdan
7 +/
8 module vibeauth.configuration;
9 
10 import vibe.data.json;
11 import std.file;
12 
13 version(unittest) {
14   import fluent.asserts;
15 }
16 
17 /// Structure used to define a service
18 struct ServiceConfiguration {
19   /// The service name
20   string name = "Unknown App";
21 
22   /// A custom style file embedded in the auth html files
23   string style;
24 
25   /// Login cookie expiration time
26   ulong loginTimeoutSeconds = 86_400;
27 
28   ///
29   Paths paths;
30 
31   ///
32   Templates templates;
33 
34   /// Load configuration from a Json object
35   void load(Json data) {
36     if("name" in data) {
37       name = data["name"].to!string;
38     }
39 
40     if("style" in data) {
41       style = data["style"].to!string;
42     }
43 
44     if("loginTimeoutSeconds" in data) {
45       loginTimeoutSeconds = data["loginTimeoutSeconds"].to!ulong;
46     }
47 
48     if("paths" in data && data["paths"].type == Json.Type.object) {
49       paths.load(data["paths"]);
50     }
51 
52     if("templates" in data && data["templates"].type == Json.Type.object) {
53       templates.load(data["templates"]);
54     }
55   }
56 }
57 
58 /// load configuration
59 unittest {
60   auto config = `{
61     "name": "demo",
62     "style": "some style",
63     "loginTimeoutSeconds": 100,
64     "paths": {
65       "location": "location"
66     }
67   }`.parseJsonString;
68 
69   ServiceConfiguration configuration;
70   configuration.load(config);
71 
72   configuration.name.should.equal("demo");
73   configuration.style.should.equal("some style");
74   configuration.loginTimeoutSeconds.should.equal(100);
75   configuration.paths.location.should.equal("location");
76 }
77 
78 ///
79 struct Paths {
80   /// The service base URL. Url used for redireaction and email links
81   string location = "http://localhost";
82 
83   ///
84   RegistrationPaths registration;
85 
86   ///
87   LoginPaths login;
88 
89   ///
90   UserManagementPaths userManagement;
91 
92   ///
93   ResourcePaths resources;
94 
95   /// Load configuration from a Json object
96   void load(Json data) {
97     if("location" in data) {
98       location = data["location"].to!string;
99     }
100 
101     if("registration" in data) {
102       registration.load(data["registration"]);
103     }
104 
105     if("registration" in data) {
106       login.load(data["login"]);
107     }
108 
109     if("registration" in data) {
110       userManagement.load(data["userManagement"]);
111     }
112   }
113 }
114 
115 ///
116 struct Templates {
117   mixin ObjectLoader;
118 
119   ///
120   RegistrationTemplates registration;
121 
122   ///
123   LoginTemplates login;
124 
125   ///
126   UserManagementTemplates userManagement;
127 }
128 
129 struct ResourcePaths {
130   ///
131   string bootstrapStyle = "/assets/bootstrap.min.css";
132   ///
133   string bootstrapJs = "/assets/bootstrap.min.js";
134   ///
135   string jquery = "/assets/jquery.min.js";
136 }
137 
138 /// Registration process url paths
139 struct RegistrationPaths {
140   mixin StringLoader;
141 
142   /// 
143   string register = "/register";
144   ///
145   string addUser = "/register/user";
146   ///
147   string activation = "/register/activation";
148   ///
149   string challange = "/register/challenge";
150   ///
151   string confirmation = "/register/confirmation";
152   ///
153   string activationRedirect = "/";
154 }
155 
156 /// Html templaes used in the registration process
157 struct RegistrationTemplates {
158   mixin FileLoader;
159 
160   ///
161   string formTemplate = import("register/formTemplate.html");
162 
163   ///
164   string form = import("register/form.html");
165 
166   ///
167   string confirmationTemplate = import("register/confirmationTemplate.html");
168   ///
169   string confirmation = import("register/confirmation.html");;
170   
171   ///
172   string successTemplate = import("register/successTemplate.html");
173   ///
174   string success = import("register/success.html");
175 }
176 
177 /// Paths for the login process
178 struct LoginPaths {
179   mixin StringLoader;
180 
181   ///
182   string form = "/login";
183 
184   ///
185   string login = "/login/check";
186 
187   ///
188   string resetForm = "/login/reset";
189 
190   ///
191   string reset = "/login/reset/send";
192 
193   ///
194   string changePassword = "/login/reset/change";
195 
196   ///
197   string redirect = "/";
198 }
199 
200 /// Html templates for the login process
201 struct LoginTemplates {
202   mixin FileLoader;
203 
204   ///
205   string formTemplate = import("login/formTemplate.html");
206   ///
207   string form = import("login/form.html");
208 
209   ///
210   string resetTemplate = import("login/resetTemplate.html");
211   ///
212   string reset = import("login/reset.html");
213   ///
214   string resetPassword = import("login/resetPasswordForm.html");
215 }
216 
217 struct UserManagementPaths {
218   mixin StringLoader;
219 
220   ///
221   string list = "/admin/users";
222 
223   ///
224   string profile = "/admin/users/:id";
225   ///
226   string updateProfile = "/admin/users/:id/update";
227 
228   ///
229   string account = "/admin/users/:id/account";
230   ///
231   string updateAccount = "/admin/users/:id/account/update";
232   ///
233   string deleteAccount = "/admin/users/:id/delete";
234 
235   ///
236   string security = "/admin/users/:id/security";
237   ///
238   string securityMakeAdmin = "/admin/users/:id/security/make-admin";
239   ///
240   string securityRevokeAdmin = "/admin/users/:id/security/revoke-admin";
241   ///
242   string updateSecurity = "/admin/users/:id/security/update";
243 }
244 
245 struct UserManagementTemplates {
246   mixin FileLoader;
247 
248   ///
249   string listTemplate = import("userManagement/template.html");
250 
251   ///
252   string userTemplate = import("userManagement/userTemplate.html");
253 
254   ///
255   string profileForm = import("userManagement/profileForm.html");
256 
257   ///
258   string accountForm = import("userManagement/accountForm.html");
259 
260   ///
261   string question = import("userManagement/question.html");
262 
263   ///
264   string securityForm = import("userManagement/securityForm.html");
265   ///
266   string adminRights = import("userManagement/adminRights.html");
267   ///
268   string otherRights = import("userManagement/otherRights.html");
269 }
270 
271 mixin template FileLoader() {
272   /// Load configuration from a Json object
273   void load(Json data) {
274     static foreach(member; __traits(allMembers, typeof(this))) {
275       static if(member != "load") {
276         if(member in data && data[member].type == Json.Type..string) {
277           string fileName = data[member].to!string;
278           mixin("this." ~ member ~ " = readText(fileName);");
279         }
280       }
281     }
282   }
283 }
284 
285 mixin template ObjectLoader() {
286   /// Load configuration from a Json object
287   void load(Json data) {
288     static foreach(member; __traits(allMembers, typeof(this))) {
289       static if(member != "load") {
290         if(member in data && data[member].type == Json.Type.object) {
291           mixin("this." ~ member ~ ".load(data[\"" ~ member ~ "\"]);");
292         }
293       }
294     }
295   }
296 }
297 
298 mixin template StringLoader() {
299   /// Load configuration from a Json object
300   void load(Json data) {
301     static foreach(member; __traits(allMembers, typeof(this))) {
302       static if(member != "load") {
303         if(member in data && data[member].type == Json.Type.object) {
304           mixin("this." ~ member ~ " = data[\"" ~ member ~ "\"].to!string;");
305         }
306       }
307     }
308   }
309 }