1 module vibeauth.authenticators.BasicAuth;
2 
3 import vibe.http.router;
4 import vibe.data.json;
5 
6 import std.algorithm.searching, std.base64, std..string, std.stdio;
7 import vibeauth.authenticators.BaseAuth;
8 
9 /// Basic auth credential pair
10 struct BasicAuthCredentials {
11   ///
12   string username;
13 
14   ///
15   string password;
16 }
17 
18 /// Basic auth handler RFC 7617. It's not safe to use it without https.
19 class BasicAuth(string realm) : BaseAuth {
20 
21   /// Instantiate the authenticator with an user collection
22   this(UserCollection collection) {
23     super(collection);
24   }
25 
26   override {
27     /// Auth handler that will fail if a successfull auth was not performed.
28     /// This handler is usefull for routes that want to hide information to the
29     /// public.
30     void mandatoryAuth(HTTPServerRequest req, HTTPServerResponse res) {
31       super.mandatoryAuth(req, res);
32     }
33 
34     /// ditto
35     AuthResult mandatoryAuth(HTTPServerRequest req) {
36       auto pauth = "Authorization" in req.headers;
37 
38       if(pauth && (*pauth).startsWith("Basic ")) {
39         auto auth = parseBasicAuth((*pauth)[6 .. $]);
40 
41         if(collection.contains(auth.username) && collection[auth.username].isValidPassword(auth.password)) {
42           req.username = auth.username;
43           return AuthResult.success;
44         }
45       }
46 
47       return AuthResult.unauthorized;
48     }
49 
50     /// Auth handler that fails only if the auth fields are present and are not valid.
51     /// This handler is usefull when a route should return different data when the user is
52     /// logged in
53     void permisiveAuth(HTTPServerRequest req, HTTPServerResponse res) {
54       super.permisiveAuth(req, res);
55     }
56 
57     /// ditto
58     AuthResult permisiveAuth(HTTPServerRequest req) {
59       auto pauth = "Authorization" in req.headers;
60 
61       if(pauth && (*pauth).startsWith("Basic ")) {
62         auto auth = parseBasicAuth((*pauth)[6 .. $]);
63 
64         if(collection.contains(auth.username) && collection[auth.username].isValidPassword(auth.password)) {
65           req.username = auth.username;
66           return AuthResult.success;
67         }
68       }
69 
70       return AuthResult.unauthorized;
71     }
72 
73     ///
74     void respondUnauthorized(HTTPServerResponse res) {
75       res.statusCode = HTTPStatus.unauthorized;
76       res.contentType = "text/plain";
77       res.headers["WWW-Authenticate"] = "Basic realm=\""~realm~"\"";
78       res.bodyWriter.write("Authorization required");
79     }
80 
81     ///
82     void respondInvalidToken(HTTPServerResponse res) {
83       respondUnauthorized(res);
84     }
85   }
86 
87   private {
88     /// Parse user input
89     BasicAuthCredentials parseBasicAuth(string data) {
90       string decodedData = cast(string) Base64.decode(data);
91       auto idx = decodedData.indexOf(":");
92       enforceBadRequest(idx >= 0, "Invalid auth string format!");
93 
94       return BasicAuthCredentials(decodedData[0 .. idx], decodedData[idx+1 .. $]);
95     }
96   }
97 }