From 9c1014ac72345cae8619b741eaccf047d1a4c09a Mon Sep 17 00:00:00 2001 From: HeshamTB Date: Mon, 29 Apr 2024 17:38:11 +0300 Subject: [PATCH] init: can do full login: ooo muh gut Signed-off-by: HeshamTB --- go.mod | 3 + internal/constants/common.go | 5 ++ internal/urls/common.go | 10 +++ main.go | 75 +++++++++++++++++++++ pkg/endpoints/common.go | 9 +++ pkg/endpoints/login.go | 123 +++++++++++++++++++++++++++++++++++ pkg/models/login.go | 59 +++++++++++++++++ 7 files changed, 284 insertions(+) create mode 100644 go.mod create mode 100644 internal/constants/common.go create mode 100644 internal/urls/common.go create mode 100644 main.go create mode 100644 pkg/endpoints/common.go create mode 100644 pkg/endpoints/login.go create mode 100644 pkg/models/login.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0ce2605 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module mystcapi + +go 1.22.2 diff --git a/internal/constants/common.go b/internal/constants/common.go new file mode 100644 index 0000000..32d10b1 --- /dev/null +++ b/internal/constants/common.go @@ -0,0 +1,5 @@ +package constants + +const ( + CONTENT_TYPE_JSON = "application/json" +) diff --git a/internal/urls/common.go b/internal/urls/common.go new file mode 100644 index 0000000..cc4d256 --- /dev/null +++ b/internal/urls/common.go @@ -0,0 +1,10 @@ +package urls + +const ( + URL_MYSTC_API_BASE = "https://mystc.stc.com.sa" + PATH_API_AUTH_BASE = URL_MYSTC_API_BASE + "/api/mystc-api-authentication" + PATH_PHONES_LIST = PATH_API_AUTH_BASE + "/phones-list" + PATH_LOGIN = PATH_API_AUTH_BASE + "/login" + PATH_LOGIN_VERIFICATION = PATH_API_AUTH_BASE + "/login-verification" +) + diff --git a/main.go b/main.go new file mode 100644 index 0000000..2fe9acc --- /dev/null +++ b/main.go @@ -0,0 +1,75 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "mystcapi/pkg/endpoints" + "mystcapi/pkg/models" + "os" + "strings" +) + +const URL_MYSTC_API_BASE = "https://mystc.stc.com.sa" +const PATH_PHONES_LIST = "/api/mystc-api-authentication/phones-list" + +func main() { + + id := flag.String("id", "", "National ID or Iqama") + pw := flag.String("password", "", "MySTC Password") + flag.Parse() + + phones, err := endpoints.GetPhonesList(*id, *pw) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + reader := bufio.NewReader(os.Stdin) + var selectedNumber models.PhoneNumber + + mainloop: + for { + for idx, n := range phones.PhoneNumbers { + fmt.Printf("%d: %s %s\n", idx, n.Number, n.Type.String()) + } + input, err := reader.ReadString('\n') + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + input = strings.Trim(input, "\n") + for idx, n := range phones.PhoneNumbers { + if input == fmt.Sprint(idx) { + selectedNumber = n + break mainloop + } + } + + } + + fmt.Printf("selectedNumber: %v\n", selectedNumber) + + loginOTP, err := endpoints.RequestLoginOTP(phones.LoginToken, selectedNumber.Number) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + fmt.Println("OTP Sent!") + fmt.Printf("loginOTP: %v\n", loginOTP) + fmt.Print("Enter OTP: ") + otp, err := reader.ReadString('\n') + otp = strings.Trim(otp, "\n") + + login, err := endpoints.VerifyLoginOTP(*id, loginOTP.OtpToken, otp) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + fmt.Println("Logged in!") + fmt.Printf("login: %v\n", login) + + +} diff --git a/pkg/endpoints/common.go b/pkg/endpoints/common.go new file mode 100644 index 0000000..99025c7 --- /dev/null +++ b/pkg/endpoints/common.go @@ -0,0 +1,9 @@ +package endpoints + +import "net/http" + +var client *http.Client + +func init() { + client = &http.Client{} +} diff --git a/pkg/endpoints/login.go b/pkg/endpoints/login.go new file mode 100644 index 0000000..9c78142 --- /dev/null +++ b/pkg/endpoints/login.go @@ -0,0 +1,123 @@ +package endpoints + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "mystcapi/internal/constants" + "mystcapi/internal/urls" + "mystcapi/pkg/models" + "net/http" +) + + +func GetPhonesList(id, password string) (models.LoginPhoneListResponse, error) { + + phoneList := models.LoginPhoneListResponse{} + + reqJson, err := json.Marshal(models.LoginPhoneListRequest{ + Username: id, + Password: password, + }) + if err != nil { + return phoneList, err + } + + resp, err := client.Post( + urls.PATH_PHONES_LIST, + constants.CONTENT_TYPE_JSON, + bytes.NewReader(reqJson), + ) + if err != nil { + return phoneList, err + } + + if resp.StatusCode != http.StatusOK { + return phoneList, fmt.Errorf("http: Login got %d", resp.StatusCode) + } + + defer resp.Body.Close() + + + body, err := io.ReadAll(resp.Body) + if err != nil { + return phoneList, err + } + + err = json.Unmarshal(body, &phoneList) + return phoneList, err +} + +func RequestLoginOTP(loginToken, phoneNumber string) (models.LoginResponse, error) { + loginResp := models.LoginResponse{} + + reqJson, err := json.Marshal( + models.LoginRequest{ + LoginToken: loginToken, + PhoneNumber: phoneNumber, + }, + ) + if err != nil { + return loginResp, err + } + + resp, err := client.Post( + urls.PATH_LOGIN, + constants.CONTENT_TYPE_JSON, + bytes.NewReader(reqJson), + ) + if err != nil { + return loginResp, err + } + + if resp.StatusCode != http.StatusOK { + return loginResp, fmt.Errorf("http: RequestLoginOTP got %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return loginResp, err + } + + err = json.Unmarshal(body, &loginResp) + return loginResp, err +} + +func VerifyLoginOTP( + username, otpToken, + otp string) (models.LoginVerificationResponse, error) { + loginVerResp := models.LoginVerificationResponse{} + + reqJson, err := json.Marshal( + models.LoginVerificationRequest{ + Username: username, + OtpToken: otpToken, + Otp: otp, + }, + ) + if err != nil { + return loginVerResp, err + } + + resp, err := client.Post( + urls.PATH_LOGIN_VERIFICATION, + constants.CONTENT_TYPE_JSON, + bytes.NewReader(reqJson), + ) + if err != nil { + return loginVerResp, err + } + if resp.StatusCode != http.StatusOK { + return loginVerResp, fmt.Errorf("http: RequestLoginOTP got %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return loginVerResp, err + } + + err = json.Unmarshal(body, &loginVerResp) + return loginVerResp, err +} + diff --git a/pkg/models/login.go b/pkg/models/login.go new file mode 100644 index 0000000..c08e8cf --- /dev/null +++ b/pkg/models/login.go @@ -0,0 +1,59 @@ +package models + +type LoginPhoneListRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type LoginPhoneListResponse struct { + PhoneNumbers []PhoneNumber `json:"phoneNumbers"` + LoginToken string `json:"loginToken"` +} + +type PhoneNumberType string +var ( + POSTPAID PhoneNumberType = "1" + PREPAID PhoneNumberType = "2" +) + +func (p PhoneNumberType) String() string { + switch p { + case "1": + return "POSTPAID" + case "2": + return "PREPAID" + default: + return "UNKOWN" + } +} + +type PhoneNumber struct { + Number string `json:"number"` + Type PhoneNumberType `json:"type"` + Contact string `json:"contact"` +} + +type LoginRequest struct { + LoginToken string `json:"loginToken"` + PhoneNumber string `json:"phoneNumber"` +} + +type LoginResponse struct { + OtpToken string `json:"otpToken"` + ExpiresIn string `json:"expiresIn"` + TokenType string `json:"tokenType"` +} + +type LoginVerificationRequest struct { + Username string `json:"username"` + OtpToken string `json:"otpToken"` + Otp string `json:"otp"` +} + +type LoginVerificationResponse struct { + AccessToken string `json:"accessToken"` + TokenType string `json:"tokenType"` + ExpiresIn string `json:"expiresIn"` + RefreshToken string `json:"refreshToken"` +} +