1 module scorpion.session;
2 
3 import std.algorithm : canFind;
4 import std..string : split, indexOf, strip;
5 import std.uuid : UUID, randomUUID, parseUUID, UUIDParsingException;
6 
7 import lighttp : ServerRequest, ServerResponse, StatusCodes, Cookie;
8 
9 import scorpion.context : Context;
10 
11 final class SessionManager {
12 
13 	public immutable string cookieName;
14 
15 	private Session[UUID] _sessions;
16 
17 	public this(string cookieName) {
18 		this.cookieName = cookieName;
19 	}
20 
21 	public void add(Session session, UUID uuid) {
22 		_sessions[uuid] = session;
23 	}
24 
25 	public Session get(ServerRequest request) {
26 		if(auto cookie = cookieName in request.cookies) {
27 			try {
28 				if(auto ret = parseUUID(idup(*cookie)) in _sessions) return *ret;
29 			} catch(UUIDParsingException) {}
30 		}
31 		return new Session(this);
32 	}
33 
34 }
35 
36 final class Session {
37 
38 	private SessionManager _sessionManager;
39 
40 	private Authentication _authentication;
41 
42 	public this(SessionManager sessionManager) {
43 		_sessionManager = sessionManager;
44 	}
45 
46 	public @property bool loggedIn() {
47 		return _authentication !is null;
48 	}
49 
50 	public @property Authentication authentication() {
51 		return _authentication;
52 	}
53 
54 	public void login(ServerResponse response, Authentication authentication) {
55 		UUID uuid = randomUUID();
56 		Cookie cookie = Cookie(_sessionManager.cookieName, uuid.toString());
57 		cookie.maxAge = 3600; // 1 hour
58 		cookie.path = "/";
59 		cookie.httpOnly = true;
60 		response.add(cookie);
61 		_sessionManager.add(this, uuid);
62 		_authentication = authentication;
63 	}
64 
65 	public void logout() {
66 		_authentication = null;
67 	}
68 
69 }
70 
71 interface Authentication {
72 
73 	public @property uint userId();
74 
75 	public @property string username();
76 
77 	public @property string[] roles();
78 
79 }
80 
81 struct Auth {
82 
83 	this(string[] roles...) {
84 		this.roles = roles;
85 	}
86 
87 	string[] roles;
88 
89 	bool test(Context context) {
90 		Session session = context.session;
91 		if(session.loggedIn) {
92 			if(roles.length == 0) return true;
93 			else foreach(role ; roles) {
94 				if(session.authentication.roles.canFind(role)) return true;
95 			}
96 		}
97 		context.response.status = StatusCodes.unauthorized;
98 		return false;
99 	}
100 
101 }
102 
103 struct AuthRedirect {
104 
105 	private string location;
106 	private Auth _auth;
107 
108 	this(string location, string[] roles...) {
109 		this.location = location;
110 		_auth = Auth(roles);
111 	}
112 
113 	bool test(Context context) {
114 		if(!_auth.test(context)) {
115 			context.response.redirect(StatusCodes.temporaryRedirect, location);
116 			return false;
117 		} else {
118 			return true;
119 		}
120 	}
121 
122 }