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 }