1 /// Written in the D programming language.
2 
3 module semitwistWeb.session;
4 
5 import std.conv;
6 import std.datetime;
7 import std.typecons;
8 import std.typetuple;
9 import std.variant;
10 
11 import vibe.vibe;
12 import mysql.db;
13 import semitwist.util.all;
14 
15 import semitwistWeb.conf;
16 import semitwistWeb.db;
17 import semitwistWeb.doc;
18 import semitwistWeb.util;
19 
20 SessionStore sessionStore;
21 SessionData[string] sessions; // Indexed by session id
22 
23 string _cookiePath;
24 @property string cookiePath()
25 {
26 	if(_cookiePath is null)
27 		_cookiePath = conf.urlBase=="/"? "/" : conf.urlBase[0..$-1];
28 	
29 	return _cookiePath;
30 }
31 
32 bool useInsecureCookies = false;
33 
34 enum LoginState
35 {
36 	Out, In
37 }
38 
39 class SessionData
40 {
41 	// Static timeoutDuration
42 	private static bool initedTimeoutDuration = false;
43 	private static Duration _timeoutDuration;
44 	@property static Duration timeoutDuration()
45 	{
46 		if(!initedTimeoutDuration)
47 			timeoutDuration = minutes(30);
48 		
49 		return _timeoutDuration;
50 	}
51 	@property static void timeoutDuration(Duration value)
52 	{
53 		_timeoutDuration = value;
54 		initedTimeoutDuration = true;
55 	}
56 
57 	// Main session data
58 	string   id;
59 	Session  session;
60 	DateTime lastAccess;  //TODO? Should this be SysTime?
61 	bool     isDummyLogin = false;
62 	string   oneShotMessage;
63 	string   postLoginUrl;  /// Empty string implies default
64 	
65 	/// The ID of the logged-in user, or null if logged out
66 	private string _userId;
67 	@property final string userId() /// ditto
68 	{
69 		return _userId;
70 	}
71 
72 	@property final bool isLoggedIn()
73 	{
74 		return _userId !is null;
75 	}
76 
77 	@property final LoginState loginState()
78 	{
79 		return isLoggedIn? LoginState.In : LoginState.Out;
80 	}
81 
82 	// Instance methods
83 	this(string id)
84 	{
85 		this.id = id;
86 	}
87 
88 	void login(Connection dbConn, string userId)
89 	{
90 		if(isLoggedIn)
91 			logout(dbConn);
92 		
93 		SessionDB(id, userId).dbInsert(dbConn);
94 
95 		this._userId = userId;
96 		isDummyLogin = false;
97 	}
98 	
99 	/// Ugly hack to "login" with a dummy account, bypassing the DB.
100 	/// Needed by the document caching/preloading system.
101 	void dummyLogin()
102 	{
103 		this._userId = "{xxxxx}";
104 		isDummyLogin = true;
105 	}
106 	
107 	void logout(Connection dbConn)
108 	{
109 		if(!isLoggedIn)
110 			return;
111 		
112 		auto oldUserId = _userId;
113 		_userId = null;
114 		
115 		if(!isDummyLogin)
116 			SessionDB(id, oldUserId).dbDelete(dbConn);
117 	}
118 	
119 	final void keepAlive()
120 	{
121 		lastAccess = cast(DateTime) Clock.currTime();
122 	}
123 	
124 	/// Ends the session if it's timed out
125 	final void checkTimeout(HTTPServerRequest req, HTTPServerResponse res)
126 	{
127 		auto now = cast(DateTime) Clock.currTime();
128 		if(now - lastAccess > timeoutDuration)
129 		{
130 			{
131 				auto dbConn = dbHelperOpenDB();
132 				logout(dbConn);
133 			}
134 
135 			sessions.remove(this.id);
136 			if(req !is null)
137 				req.session = Session();
138 			res.terminateSession();
139 		}
140 	}
141 	
142 	void restoreSession(Connection dbConn)
143 	{
144 		// Do nothing
145 	}
146 	
147 	static SessionData restore(TSession)(Connection dbConn, SessionDB dbSess)
148 	{
149 		// Restore basic info
150 		SessionData sess = new TSession(dbSess.id);
151 		sess._userId = dbSess.userId;
152 		sess.keepAlive();
153 
154 		// Attach to Vibe.d session list
155 		sessionStore.set(dbSess.id, "__dummy__", Variant(""));
156 		auto vibeSess = sessionStore.open(dbSess.id);
157 		sess.session = vibeSess;
158 		sess.session.set("$sessionCookiePath", cookiePath);
159 		sess.session.set("$sessionCookieSecure", to!string(!useInsecureCookies));
160 
161 		// Add to ADAMS session list
162 		sessions[dbSess.id] = sess;
163 		
164 		// Restore data
165 		sess.restoreSession(dbConn);
166 		
167 		return sess;
168 	}
169 }
170 
171 void restoreSessions(TSession)(Connection dbConn)
172 {
173 	auto dbSessions = SessionDB.getAll(dbConn);
174 	foreach(dbSess; dbSessions)
175 		SessionData.restore!TSession(dbConn, dbSess);
176 }