1 module hunt.framework.auth.Auth; 2 3 import hunt.framework.auth.AuthOptions; 4 import hunt.framework.auth.AuthService; 5 import hunt.framework.auth.Claim; 6 import hunt.framework.auth.ClaimTypes; 7 import hunt.framework.auth.guard; 8 import hunt.framework.auth.Identity; 9 import hunt.framework.auth.JwtToken; 10 import hunt.framework.auth.JwtUtil; 11 import hunt.framework.auth.UserService; 12 import hunt.framework.auth.UserDetails; 13 import hunt.framework.http.Request; 14 // import hunt.framework.Simplify; 15 import hunt.framework.provider.ServiceProvider; 16 17 import hunt.http.AuthenticationScheme; 18 import hunt.logging.ConsoleLogger; 19 import hunt.shiro.Exceptions; 20 import hunt.shiro.authc.AuthenticationToken; 21 import hunt.util.TypeUtils; 22 23 import hunt.jwt.JwtRegisteredClaimNames; 24 25 import std.algorithm; 26 import std.array : split; 27 import std.base64; 28 import std.json; 29 import std.format; 30 import std.range; 31 import std.variant; 32 import core.time; 33 34 private enum AuthState { 35 Auto, 36 Token, 37 SignIn, 38 SignOut 39 } 40 41 42 /** 43 * 44 */ 45 class Auth { 46 47 private Identity _user; 48 private string _token; 49 private bool _remember = false; 50 private bool _isTokenRefreshed = false; 51 private bool _isLogout = false; 52 private AuthState _state = AuthState.Auto; 53 private string _guardName = DEFAULT_GURAD_NAME; 54 private Guard _guard; 55 private bool _isEnabled = false; 56 57 private Request _request; 58 59 this(Request request) { 60 _request = request; 61 _guardName = request.guardName(); 62 AuthService authService = serviceContainer().resolve!AuthService(); 63 64 _guard = authService.guard(_guardName); 65 _isEnabled = isGuardAvailable(); 66 _user = new Identity(_guardName, _isEnabled); 67 68 version(HUNT_AUTH_DEBUG) { 69 if(_isEnabled) { 70 warningf("path: %s, isAuthenticated: %s", request.path(), _user.isAuthenticated()); 71 } 72 } 73 } 74 75 bool isGuardAvailable() { 76 return _guard !is null; 77 } 78 79 bool isEnabled() { 80 return _isEnabled; 81 } 82 83 string tokenCookieName() { 84 return guard().tokenCookieName(); 85 } 86 87 // void autoDetect() { 88 // if(_state != AuthState.Auto || !isEnabled()) 89 // return; 90 91 // version(HUNT_DEBUG) { 92 // infof("Detecting the authentication state from %s", tokenCookieName()); 93 // } 94 95 // AuthenticationScheme scheme = guard().authScheme(); 96 // if(scheme == AuthenticationScheme.None) 97 // scheme = AuthenticationScheme.Bearer; 98 99 // // Detect the auth type automatically 100 // if(scheme == AuthenticationScheme.Bearer) { 101 // _token = _request.bearerToken(); 102 // } else if(scheme == AuthenticationScheme.Basic) { 103 // _token = _request.basicToken(); 104 // } 105 106 // if(_token.empty()) { // Detect the token from cookie 107 // _token = request.cookie(tokenCookieName()); 108 // } 109 110 // if(!_token.empty()) { 111 // _user.authenticate(_token, scheme); 112 // } 113 114 // _state = AuthState.Token; 115 // } 116 117 Identity user() { 118 return _user; 119 } 120 121 Guard guard() { 122 version(HUNT_DEBUG) { 123 if(!isEnabled()) { 124 string msg = format("No guard avaliable for %s", _guardName); 125 throw new AuthenticationException(msg); 126 } 127 } 128 return _guard; 129 } 130 131 Identity signIn(string name, string password, bool remember = false) { 132 _user.authenticate(name, password, remember); 133 134 _remember = remember; 135 _state = AuthState.SignIn; 136 137 if(!_user.isAuthenticated()) 138 return _user; 139 140 if(scheme == AuthenticationScheme.Bearer) { 141 UserDetails userDetails = _user.userDetails(); 142 string salt = userDetails.salt; 143 // UserService userService = guard().userService(); 144 // string salt = userService.getSalt(name, password); 145 146 uint exp = guard().tokenExpiration; // config().auth.tokenExpiration; 147 148 JSONValue claims; 149 claims["user_id"] = _user.id; 150 151 Claim[] userClaims = _user.claims(); 152 153 foreach(Claim c; userClaims) { 154 string claimName = toJwtClaimName(c.type()); 155 Variant value = c.value; 156 if(TypeUtils.isIntegral(value.type)) 157 claims[claimName] = JSONValue(c.value.get!(long)); 158 else if(TypeUtils.isUsignedIntegral(value.type)) 159 claims[claimName] = JSONValue(c.value.get!(ulong)); 160 else if(TypeUtils.isFloatingPoint(value.type)) 161 claims[claimName] = JSONValue(c.value.get!(float)); 162 else 163 claims[claimName] = JSONValue(c.value.toString()); 164 } 165 166 _token = JwtUtil.sign(name, salt, exp.seconds, claims); 167 _isEnabled = true; 168 } else if(scheme == AuthenticationScheme.Basic) { 169 string str = name ~ ":" ~ password; 170 ubyte[] data = cast(ubyte[])str; 171 _token = cast(string)Base64.encode(data); 172 _isEnabled = true; 173 } else { 174 error("Unsupported AuthenticationScheme: %s", scheme); 175 _isEnabled = false; 176 } 177 178 return _user; 179 } 180 181 static string toJwtClaimName(string name) { 182 switch(name) { 183 case ClaimTypes.Name: 184 return JwtRegisteredClaimNames.Sub; 185 186 case ClaimTypes.Nickname: 187 return JwtRegisteredClaimNames.Nickname; 188 189 case ClaimTypes.GivenName: 190 return JwtRegisteredClaimNames.GivenName; 191 192 case ClaimTypes.Surname: 193 return JwtRegisteredClaimNames.FamilyName; 194 195 case ClaimTypes.Email: 196 return JwtRegisteredClaimNames.Email; 197 198 case ClaimTypes.Gender: 199 return JwtRegisteredClaimNames.Gender; 200 201 case ClaimTypes.DateOfBirth: 202 return JwtRegisteredClaimNames.Birthdate; 203 204 default: 205 return name; 206 } 207 } 208 209 /// Use token to login 210 Identity signIn() { 211 scope(success) { 212 _state = AuthState.Token; 213 } 214 215 Guard g = guard(); 216 217 version(HUNT_DEBUG) infof("guard: %s, type: %s", g.name, typeid(g)); 218 219 AuthenticationToken token = g.getToken(_request); 220 _user.login(token); 221 _isEnabled = true; 222 return _user; 223 } 224 225 void signOut() { 226 _state = AuthState.SignOut; 227 _token = null; 228 _remember = false; 229 _isLogout = true; 230 231 if(scheme != AuthenticationScheme.Basic && scheme != AuthenticationScheme.Bearer) { 232 warningf("Unsupported authentication scheme: %s", scheme); 233 } 234 235 if(_user.isAuthenticated()) { 236 _user.logout(); 237 } 238 } 239 240 string refreshToken(string salt) { 241 string username = _user.name(); 242 if(!_user.isAuthenticated()) { 243 throw new AuthenticationException( format("The use is not authenticated: %s", _user.name())); 244 } 245 246 if(scheme == AuthenticationScheme.Bearer) { 247 // UserService userService = serviceContainer().resolve!UserService(); 248 // FIXME: Needing refactor or cleanup -@zhangxueping at 2020-07-17T11:10:18+08:00 249 // 250 // string salt = userService.getSalt(username, "no password"); 251 _token = JwtUtil.sign(username, salt); 252 } 253 254 _state = AuthState.Token; 255 _isTokenRefreshed = true; 256 return _token; 257 } 258 259 // the token value for the "remember me" session. 260 string token() { 261 // autoDetect(); 262 if(_token.empty) { 263 AuthenticationToken token = guard().getToken(_request); 264 if(token !is null) 265 _token = token.getPrincipal(); 266 } 267 return _token; 268 } 269 270 AuthenticationScheme scheme() { 271 return guard().authScheme(); 272 } 273 274 bool canRememberMe() { 275 return _remember; 276 } 277 278 bool isTokenRefreshed() { 279 return _isTokenRefreshed; 280 } 281 282 bool isLogout() { 283 return _isLogout; 284 } 285 286 void touchSession() { 287 _user.touchSession(); 288 } 289 290 }