package mrktplaats import ( "context" "fmt" ) // AuthService handles login and two-factor authentication. type AuthService struct { t *transport } // LoginRequest is the payload for initiating a login. type LoginRequest struct { Email string `json:"email"` Password string `json:"password"` Type string `json:"type"` VerificationParameters verificationParameters `json:"verificationParameters"` } type verificationParameters struct { MobileTokenForSMS *string `json:"mobileTokenForSms"` } // LoginResponse is returned from a login attempt. If 2FA is required, // Verification is populated. If login succeeds immediately, Auth is populated. type LoginResponse struct { Verification *Verification `json:"verification,omitempty"` Auth *AuthToken `json:"auth,omitempty"` User *User `json:"user,omitempty"` } // Verification contains the 2FA challenge details. type Verification struct { RequestID string `json:"requestId"` Method string `json:"method"` Message string `json:"message"` } // AuthToken contains the access and refresh tokens. type AuthToken struct { AccessToken string `json:"accessToken"` RefreshToken string `json:"refreshToken"` ExpiresIn string `json:"expiresIn"` } // AuthResponse is the full response after successful authentication. type AuthResponse struct { Auth AuthToken `json:"auth"` User User `json:"user"` } // Login initiates authentication with email and password. // If 2FA is required, the response contains a Verification with a RequestID // that must be passed to VerifyCode. func (s *AuthService) Login(ctx context.Context, email, password string) (*LoginResponse, error) { body := LoginRequest{ Email: email, Password: password, Type: "user-credentials", VerificationParameters: verificationParameters{MobileTokenForSMS: nil}, } var resp LoginResponse if err := s.t.postJSON(ctx, "/app/identity/v3/login", nil, nil, body, &resp); err != nil { return nil, err } if resp.Auth != nil { s.t.setAccessToken(resp.Auth.AccessToken) } return &resp, nil } // RefreshToken exchanges a refresh token for a new access token without requiring // the user to re-enter their credentials. The response contains a fresh AuthToken // with a new accessToken, refreshToken, and expiresIn. func (s *AuthService) RefreshToken(ctx context.Context, refreshToken string) (*AuthToken, error) { body := struct { RefreshToken string `json:"refreshToken"` }{RefreshToken: refreshToken} var resp struct { Auth AuthToken `json:"auth"` } if err := s.t.postJSON(ctx, "/app/identity/v3/refreshtoken", nil, nil, body, &resp); err != nil { return nil, err } s.t.setAccessToken(resp.Auth.AccessToken) return &resp.Auth, nil } // VerifyCode completes 2FA by submitting the SMS verification code. // On success the access token is automatically set on the client. func (s *AuthService) VerifyCode(ctx context.Context, requestID, code string) (*AuthResponse, error) { path := fmt.Sprintf("/app/identity/v3/two-factor-auth/verification/requests/%s/verify-code", requestID) body := struct { Key *string `json:"key"` MagicToken *string `json:"magicToken"` VerificationCode string `json:"verificationCode"` VerificationParameters verificationParameters `json:"verificationParameters"` }{ VerificationCode: code, } var resp AuthResponse if err := s.t.postJSON(ctx, path, nil, nil, body, &resp); err != nil { return nil, err } s.t.setAccessToken(resp.Auth.AccessToken) return &resp, nil }