@@ -4,95 +4,112 @@ import (
44 "encoding/base64"
55 "encoding/json"
66 "errors"
7- "fmt"
87 "net/http"
98 "time"
109
10+ "github.com/anyshake/observer/pkg/logger"
1111 "github.com/dchest/captcha"
1212)
1313
14+ var (
15+ errInvalidLoginRequest = errors .New ("invalid login request" )
16+ errAuthenticationFailed = errors .New ("authentication failed" )
17+ )
18+
1419func (h * auth ) login (sessionId , secret , nonce , challengeId , challengeSolution , captchaId , captchaVal , payload , userAgent , userIp string ) (code int , userId string , err error ) {
20+ fail := func (status int , public error , internal string , args ... any ) (int , string , error ) {
21+ if internal != "" {
22+ logger .GetLogger (LOG_PREFIX ).Errorf (internal , args ... )
23+ }
24+ return status , "" , public
25+ }
26+
1527 if sessionId == "" || secret == "" || nonce == "" || challengeId == "" || challengeSolution == "" || captchaId == "" || captchaVal == "" || payload == "" {
16- return http .StatusBadRequest , "" , errors . New ( "failed to login: missing required fields in request " )
28+ return fail ( http .StatusBadRequest , errInvalidLoginRequest , " login rejected : missing required fields" )
1729 }
1830
1931 // 1. Check if the nonce is valid and not reused
2032 nc , err := base64 .StdEncoding .DecodeString (nonce )
2133 if err != nil {
22- return http .StatusBadRequest , "" , errors . New ( "failed to login: provided nonce is invalid" )
34+ return fail ( http .StatusBadRequest , errInvalidLoginRequest , " login rejected: invalid nonce encoding: %v" , err )
2335 }
2436 ncStr := string (nc )
2537 if t , ok := h .nonceCache .Get (ncStr ); ok && time .Since (t ) <= time .Hour {
26- return http .StatusForbidden , "" , errors . New ( "failed to login: replay attack detected" )
38+ return fail ( http .StatusForbidden , errAuthenticationFailed , " login rejected: nonce replay detected" )
2739 }
2840 h .nonceCache .Add (ncStr , time .Now ())
2941
3042 // 2. Verify PoW challenge solution
3143 challenge , ok := h .authChallengePool .GetAndDel (challengeId )
3244 if ! ok {
33- return http .StatusUnauthorized , "" , errors . New ( "failed to login: invalid pre-auth challenge ID " )
45+ return fail ( http .StatusUnauthorized , errAuthenticationFailed , " login rejected: unknown challenge id " )
3446 }
3547 if ! challenge .isChallengeAlive () {
36- return http .StatusUnauthorized , "" , errors . New ( "failed to login: pre-auth challenge has expired, please try again " )
48+ return fail ( http .StatusUnauthorized , errAuthenticationFailed , " login rejected: challenge expired" )
3749 }
38- if ! challenge .verifyChallenge (challengeId , challengeSolution ) {
39- return http .StatusUnauthorized , "" , errors . New ( "failed to login: invalid challenge solution" )
50+ if ! challenge .verifyChallenge (challengeSolution ) {
51+ return fail ( http .StatusUnauthorized , errAuthenticationFailed , " login rejected : invalid PoW solution" )
4052 }
4153
4254 // 3. Verify captcha solution
4355 if ! captcha .VerifyString (captchaId , captchaVal ) {
44- return http .StatusUnauthorized , "" , fmt . Errorf ( "failed to login: provided captcha solution %s is invalid" , captchaVal )
56+ return fail ( http .StatusUnauthorized , errAuthenticationFailed , " login rejected: invalid captcha" )
4557 }
4658
4759 // 4. Extract RSA key pair from session ID
48- kp , ok := h .keyPairDataPool .GetAndDel (sessionId )
60+ // Key pairs are shared across preauth requests within TTL, so do not delete on use.
61+ kp , ok := h .keyPairDataPool .Get (sessionId )
4962 if ! ok {
50- return http .StatusUnauthorized , "" , errors . New ( "failed to login: provided session ID is invalid " )
63+ return fail ( http .StatusUnauthorized , errAuthenticationFailed , " login rejected: unknown session id " )
5164 }
5265 if ! kp .isKeyPairAlive () {
53- return http .StatusUnauthorized , "" , errors .New ("failed to login: provided session ID has expired" )
66+ h .keyPairDataPool .Del (sessionId )
67+ return fail (http .StatusUnauthorized , errAuthenticationFailed , "login rejected: session expired" )
5468 }
5569
5670 // 5. Attempt to decrypt RSA encrypted AES secret from payload
5771 aesSecretBytesB64 , err := kp .rsaKeyPair .Decrypt ([]byte (secret ), true )
5872 if err != nil {
59- return http .StatusUnauthorized , "" , fmt . Errorf ( "failed to login: failed to decrypt secret : %w " , err )
73+ return fail ( http .StatusUnauthorized , errAuthenticationFailed , " login rejected: secret decrypt failed : %v " , err )
6074 }
6175 aesSecretBytes , err := base64 .StdEncoding .DecodeString (string (aesSecretBytesB64 ))
6276 if err != nil {
63- return http .StatusUnauthorized , "" , fmt . Errorf ( "failed to login: failed to decode secret : %w " , err )
77+ return fail ( http .StatusUnauthorized , errAuthenticationFailed , " login rejected: secret decode failed : %v " , err )
6478 }
6579 encryptor := newAES256GCM (aesSecretBytes )
80+ if encryptor == nil {
81+ return fail (http .StatusUnauthorized , errAuthenticationFailed , "login rejected: failed to init AES-GCM" )
82+ }
6683
6784 // 6. Additional check to ensure data integrity (defense in depth)
6885 if _ , err := encryptor .decrypt (nc , []byte (sessionId )); err != nil {
69- return http .StatusForbidden , "" , fmt . Errorf ( "failed to login: malformed nonce received : %w " , err )
86+ return fail ( http .StatusForbidden , errAuthenticationFailed , " login rejected : malformed nonce: %v " , err )
7087 }
7188
7289 // 7. Attempt to decrypt AES encrypted payload containing credential
7390 pl , err := base64 .StdEncoding .DecodeString (payload )
7491 if err != nil {
75- return http .StatusBadRequest , "" , errors . New ( "failed to login: provided payload is invalid" )
92+ return fail ( http .StatusBadRequest , errInvalidLoginRequest , " login rejected: invalid payload encoding: %v" , err )
7693 }
7794 credential , err := encryptor .decrypt (pl , []byte (sessionId ))
7895 if err != nil {
79- return http .StatusUnauthorized , "" , fmt . Errorf ( "failed to login: failed to decrypt payload : %w " , err )
96+ return fail ( http .StatusUnauthorized , errAuthenticationFailed , " login rejected: payload decrypt failed : %v " , err )
8097 }
8198
8299 // 8. Unmarshal credential and perform login logic
83100 var credentialMap map [string ]any
84101 if err = json .Unmarshal (credential , & credentialMap ); err != nil {
85- return http .StatusUnauthorized , "" , fmt . Errorf ( "failed to login: failed to unmarshal payload : %w " , err )
102+ return fail ( http .StatusUnauthorized , errAuthenticationFailed , " login rejected: credential unmarshal failed : %v " , err )
86103 }
87104 username , usernameOk := credentialMap ["username" ].(string )
88105 password , passwordOk := credentialMap ["password" ].(string )
89106 if ! usernameOk || ! passwordOk {
90- return http .StatusUnauthorized , "" , errors . New ( "failed to login: provided credential format is invalid" )
107+ return fail ( http .StatusUnauthorized , errAuthenticationFailed , " login rejected: credential format invalid" )
91108 }
92109
93110 userId , err = h .actionHandler .SysUserLogin (username , password , userAgent , userIp )
94111 if err != nil {
95- return http .StatusUnauthorized , "" , fmt . Errorf ( " user login failed: %w" , err )
112+ return fail ( http .StatusUnauthorized , errAuthenticationFailed , "login rejected: user auth failed for %s : %v" , username , err )
96113 }
97114
98115 return http .StatusOK , userId , nil
0 commit comments