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; 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 if(!isEnabled()) { 123 string msg = format("No guard avaliable for %s", _guardName); 124 throw new AuthenticationException(msg); 125 } 126 return _guard; 127 } 128 129 Identity signIn(string name, string password, bool remember = false) { 130 _user.authenticate(name, password, remember); 131 132 _remember = remember; 133 _state = AuthState.SignIn; 134 135 if(!_user.isAuthenticated()) 136 return _user; 137 138 if(scheme == AuthenticationScheme.Bearer) { 139 UserDetails userDetails = _user.userDetails(); 140 string salt = userDetails.salt; 141 // UserService userService = guard().userService(); 142 // string salt = userService.getSalt(name, password); 143 144 uint exp = guard().tokenExpiration; // config().auth.tokenExpiration; 145 146 JSONValue claims; 147 claims["user_id"] = _user.id; 148 149 Claim[] userClaims = _user.claims(); 150 151 foreach(Claim c; userClaims) { 152 string claimName = toJwtClaimName(c.type()); 153 Variant value = c.value; 154 if(TypeUtils.isIntegral(value.type)) 155 claims[claimName] = JSONValue(c.value.get!(long)); 156 else if(TypeUtils.isUsignedIntegral(value.type)) 157 claims[claimName] = JSONValue(c.value.get!(ulong)); 158 else if(TypeUtils.isFloatingPoint(value.type)) 159 claims[claimName] = JSONValue(c.value.get!(float)); 160 else 161 claims[claimName] = JSONValue(c.value.toString()); 162 } 163 164 _token = JwtUtil.sign(name, salt, exp.seconds, claims); 165 _isEnabled = true; 166 } else if(scheme == AuthenticationScheme.Basic) { 167 string str = name ~ ":" ~ password; 168 ubyte[] data = cast(ubyte[])str; 169 _token = cast(string)Base64.encode(data); 170 _isEnabled = true; 171 } else { 172 error("Unsupported AuthenticationScheme: %s", scheme); 173 _isEnabled = false; 174 } 175 176 return _user; 177 } 178 179 static string toJwtClaimName(string name) { 180 switch(name) { 181 case ClaimTypes.Name: 182 return JwtRegisteredClaimNames.Sub; 183 184 case ClaimTypes.Nickname: 185 return JwtRegisteredClaimNames.Nickname; 186 187 case ClaimTypes.GivenName: 188 return JwtRegisteredClaimNames.GivenName; 189 190 case ClaimTypes.Surname: 191 return JwtRegisteredClaimNames.FamilyName; 192 193 case ClaimTypes.Email: 194 return JwtRegisteredClaimNames.Email; 195 196 case ClaimTypes.Gender: 197 return JwtRegisteredClaimNames.Gender; 198 199 case ClaimTypes.DateOfBirth: 200 return JwtRegisteredClaimNames.Birthdate; 201 202 default: 203 return name; 204 } 205 } 206 207 /// Use token to login 208 Identity signIn() { 209 scope(success) { 210 _state = AuthState.Token; 211 } 212 213 Guard g = guard(); 214 215 version(HUNT_DEBUG) infof("guard: %s, type: %s", g.name, typeid(g)); 216 217 AuthenticationToken token = g.getToken(_request); 218 _user.login(token); 219 _isEnabled = true; 220 return _user; 221 } 222 223 void signOut() { 224 _state = AuthState.SignOut; 225 _token = null; 226 _remember = false; 227 _isLogout = true; 228 229 if(scheme != AuthenticationScheme.Basic && scheme != AuthenticationScheme.Bearer) { 230 warningf("Unsupported authentication scheme: %s", scheme); 231 } 232 233 if(_user.isAuthenticated()) { 234 _user.logout(); 235 } 236 } 237 238 string refreshToken(string salt) { 239 string username = _user.name(); 240 if(!_user.isAuthenticated()) { 241 throw new AuthenticationException( format("The use is not authenticated: %s", _user.name())); 242 } 243 244 if(scheme == AuthenticationScheme.Bearer) { 245 // UserService userService = serviceContainer().resolve!UserService(); 246 // FIXME: Needing refactor or cleanup -@zhangxueping at 2020-07-17T11:10:18+08:00 247 // 248 // string salt = userService.getSalt(username, "no password"); 249 _token = JwtUtil.sign(username, salt); 250 } 251 252 _state = AuthState.Token; 253 _isTokenRefreshed = true; 254 return _token; 255 } 256 257 // the token value for the "remember me" session. 258 string token() { 259 // autoDetect(); 260 if(_token.empty) { 261 AuthenticationToken token = guard().getToken(_request); 262 if(token !is null) 263 _token = token.getPrincipal(); 264 } 265 return _token; 266 } 267 268 AuthenticationScheme scheme() { 269 return guard().authScheme(); 270 } 271 272 bool canRememberMe() { 273 return _remember; 274 } 275 276 bool isTokenRefreshed() { 277 return _isTokenRefreshed; 278 } 279 280 bool isLogout() { 281 return _isLogout; 282 } 283 284 void touchSession() { 285 _user.touchSession(); 286 } 287 288 }