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 }