diff --git a/config/types.go b/config/types.go index 21e84aa..db035dc 100644 --- a/config/types.go +++ b/config/types.go @@ -68,8 +68,10 @@ type ttl struct { } type kyc struct { - AliAccessKeyId string `yaml:"ali_access_key_id"` - AliAccessKeySecret string `yaml:"ali_access_key_secret"` + AliAccessKeyId string `yaml:"ali_access_key_id"` + AliAccessKeySecret string `yaml:"ali_access_key_secret"` + PassportReaderPublicKey string `yaml:"passport_reader_public_key"` + PassportReaderSecret string `yaml:"passport_reader_secret"` } type tracer struct { diff --git a/internal/ali_cnrid/types.go b/internal/ali_cnrid/types.go deleted file mode 100644 index 9aa6f39..0000000 --- a/internal/ali_cnrid/types.go +++ /dev/null @@ -1,7 +0,0 @@ -package kyc - -type KycAli struct { - ParamType string `json:"param_type"` - IdentifyNum string `json:"identify_num"` - UserName string `json:"user_name"` -} diff --git a/internal/ali_cnrid/kyc.go b/internal/kyc/cnrid.go similarity index 63% rename from internal/ali_cnrid/kyc.go rename to internal/kyc/cnrid.go index 56baa46..44dd836 100644 --- a/internal/ali_cnrid/kyc.go +++ b/internal/kyc/cnrid.go @@ -2,13 +2,8 @@ package kyc import ( "crypto/md5" - "encoding/base64" "encoding/hex" - "encoding/json" - "errors" "fmt" - "nixcn-cms/internal/cryptography" - "nixcn-cms/internal/kyc" "unicode/utf8" alicloudauth20190307 "github.com/alibabacloud-go/cloudauth-20190307/v4/client" @@ -19,61 +14,17 @@ import ( "github.com/spf13/viper" ) -func DecodeB64Json(b64Json string) (*kyc.KycInfo, error) { - rawJson, err := base64.StdEncoding.DecodeString(b64Json) - if err != nil { - return nil, errors.New("[KYC] invalid base64 json") - } - - var kycInfo kyc.KycInfo - if err := json.Unmarshal(rawJson, &kycInfo); err != nil { - return nil, errors.New("[KYC] invalid json structure") - } - - return &kycInfo, nil -} - -func EncodeAES(kyc *kyc.KycInfo) (*string, error) { - plainJson, err := json.Marshal(kyc) - if err != nil { - return nil, err - } - - aesKey := viper.GetString("secrets.kyc_info_key") - encrypted, err := cryptography.AESCBCEncrypt(plainJson, []byte(aesKey)) - if err != nil { - return nil, err - } - - return &encrypted, nil -} - -func DecodeAES(cipherStr string) (*kyc.KycInfo, error) { - aesKey := viper.GetString("secrets.kyc_info_key") - plainBytes, err := cryptography.AESCBCDecrypt(cipherStr, []byte(aesKey)) - if err != nil { - return nil, err - } - - var kycInfo kyc.KycInfo - if err := json.Unmarshal(plainBytes, &kycInfo); err != nil { - return nil, errors.New("[KYC] invalid decrypted json") - } - - return &kycInfo, nil -} - -func MD5AliEnc(kyc *kyc.KycInfo) (*KycAli, error) { +func CNRidMD5AliEnc(kyc *KycInfo) (*AliCloudAuth, error) { if kyc.Type != "Chinese" { return nil, nil } // MD5 Legal Name rule: First Chinese char md5enc, remaining plain, at least 2 Chinese chars - if len(kyc.LegalName) < 2 || utf8.RuneCountInString(kyc.LegalName) < 2 { + if len(kyc.CNRidInfo.LegalName) < 2 || utf8.RuneCountInString(kyc.CNRidInfo.LegalName) < 2 { return nil, fmt.Errorf("input string must have at least 2 Chinese characters") } - lnFirstRune, size := utf8.DecodeRuneInString(kyc.LegalName) + lnFirstRune, size := utf8.DecodeRuneInString(kyc.CNRidInfo.LegalName) if lnFirstRune == utf8.RuneError { return nil, fmt.Errorf("invalid first character") } @@ -82,18 +33,18 @@ func MD5AliEnc(kyc *kyc.KycInfo) (*KycAli, error) { lnHash.Write([]byte(string(lnFirstRune))) lnFirstHash := hex.EncodeToString(lnHash.Sum(nil)) - lnRemaining := kyc.LegalName[size:] + lnRemaining := kyc.CNRidInfo.LegalName[size:] ln := lnFirstHash + lnRemaining // MD5 Resident Id rule: First 6 char plain, middle birthdate md5enc, last 4 char plain, at least 18 chars - if len(kyc.ResidentId) < 18 { + if len(kyc.CNRidInfo.ResidentId) < 18 { return nil, fmt.Errorf("input string must have at least 18 characters") } - ridPrefix := kyc.ResidentId[:6] - ridSuffix := kyc.ResidentId[len(kyc.ResidentId)-4:] - ridMiddle := kyc.ResidentId[6 : len(kyc.ResidentId)-4] + ridPrefix := kyc.CNRidInfo.ResidentId[:6] + ridSuffix := kyc.CNRidInfo.ResidentId[len(kyc.CNRidInfo.ResidentId)-4:] + ridMiddle := kyc.CNRidInfo.ResidentId[6 : len(kyc.CNRidInfo.ResidentId)-4] ridHash := md5.New() ridHash.Write([]byte(ridMiddle)) @@ -102,7 +53,7 @@ func MD5AliEnc(kyc *kyc.KycInfo) (*KycAli, error) { rid := ridPrefix + ridMiddleHash + ridSuffix // Aliyun Id2MetaVerify API Params - var kycAli KycAli + var kycAli AliCloudAuth kycAli.ParamType = "md5" kycAli.UserName = ln kycAli.IdentifyNum = rid @@ -110,7 +61,7 @@ func MD5AliEnc(kyc *kyc.KycInfo) (*KycAli, error) { return &kycAli, nil } -func AliId2MetaVerify(kycAli *KycAli) (*string, error) { +func AliId2MetaVerify(kycAli *AliCloudAuth) (*string, error) { // Create aliyun openapi credential credentialConfig := new(alicredential.Config). SetType("access_key"). diff --git a/internal/kyc/crypto.go b/internal/kyc/crypto.go new file mode 100644 index 0000000..3d3ae77 --- /dev/null +++ b/internal/kyc/crypto.go @@ -0,0 +1,39 @@ +package kyc + +import ( + "encoding/json" + "errors" + "nixcn-cms/internal/cryptography" + + "github.com/spf13/viper" +) + +func EncodeAES(kyc *KycInfo) (*string, error) { + plainJson, err := json.Marshal(kyc) + if err != nil { + return nil, err + } + + aesKey := viper.GetString("secrets.kyc_info_key") + encrypted, err := cryptography.AESCBCEncrypt(plainJson, []byte(aesKey)) + if err != nil { + return nil, err + } + + return &encrypted, nil +} + +func DecodeAES(cipherStr string) (*KycInfo, error) { + aesKey := viper.GetString("secrets.kyc_info_key") + plainBytes, err := cryptography.AESCBCDecrypt(cipherStr, []byte(aesKey)) + if err != nil { + return nil, err + } + + var kycInfo KycInfo + if err := json.Unmarshal(plainBytes, &kycInfo); err != nil { + return nil, errors.New("[KYC] invalid decrypted json") + } + + return &kycInfo, nil +} diff --git a/internal/kyc/passport.go b/internal/kyc/passport.go new file mode 100644 index 0000000..2f14306 --- /dev/null +++ b/internal/kyc/passport.go @@ -0,0 +1,88 @@ +package kyc + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/spf13/viper" +) + +func CreateSession() (*PassportReaderSessionResponse, error) { + publicKey := viper.GetString("kyc.passport_reader_public_key") + secret := viper.GetString("kyc.passport_reader_secret") + + apiURL := "https://passportreader.app/api/v1/session.create" + + client := &http.Client{ + Timeout: 10 * time.Second, + } + + req, err := http.NewRequest("POST", apiURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.SetBasicAuth(publicKey, secret) + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("api returned error: %d, body: %s", resp.StatusCode, string(body)) + } + + var sessionResp PassportReaderSessionResponse + if err := json.NewDecoder(resp.Body).Decode(&sessionResp); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &sessionResp, nil +} + +func GetSessionDetails(sessionID int) (*PassportReaderSessionDetailResponse, error) { + publicKey := viper.GetString("kyc.passport_reader_public_key") + secret := viper.GetString("kyc.passport_reader_secret") + + apiURL := "https://passportreader.app/api/v1/session.get" + + reqPayload := PassportReaderGetSessionRequest{ID: sessionID} + jsonData, err := json.Marshal(reqPayload) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.SetBasicAuth(publicKey, secret) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{Timeout: 15 * time.Second} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("api error: %d, response: %s", resp.StatusCode, string(body)) + } + + var detailResp PassportReaderSessionDetailResponse + if err := json.NewDecoder(resp.Body).Decode(&detailResp); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &detailResp, nil +} diff --git a/internal/kyc/types.go b/internal/kyc/types.go index 9bdbece..84a865a 100644 --- a/internal/kyc/types.go +++ b/internal/kyc/types.go @@ -1,17 +1,48 @@ package kyc type KycInfo struct { - Type string `json:"type"` // cnrid/passport - LegalName string `json:"legal_name"` - ResidentId string `json:"rsident_id"` - PassportInfo PassportInfo `json:"passport_info"` + Type string `json:"type"` // cnrid/passport + CNRidInfo *CNRidInfo `json:"CNRodInfo"` + PassportInfo *PassportInfo `json:"passport_info"` +} + +type CNRidInfo struct { + LegalName string `json:"legal_name"` + ResidentId string `json:"resident_id"` } type PassportInfo struct { - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - DateOfExpire string `json:"date_of_expire"` + GivenNames string `json:"given_names"` + Surname string `json:"surname"` Nationality string `json:"nationality"` + DateOfBirth string `json:"date_of_birth"` DocumentType string `json:"document_type"` DocumentNumber string `json:"document_number"` + ExpiryDate string `json:"expiry_date"` +} + +type AliCloudAuth struct { + ParamType string `json:"param_type"` + IdentifyNum string `json:"identify_num"` + UserName string `json:"user_name"` +} + +type PassportReaderSessionResponse struct { + ID int `json:"id"` + Token string `json:"token"` +} + +type PassportReaderGetSessionRequest struct { + ID int `json:"id"` +} + +type PassportReaderSessionDetailResponse struct { + State string `json:"state"` + GivenNames string `json:"given_names"` + Surname string `json:"surname"` + Nationality string `json:"nationality"` + DateOfBirth string `json:"date_of_birth"` + DocumentType string `json:"document_type"` + DocumentNumber string `json:"document_number"` + ExpiryDate string `json:"expiry_date"` } diff --git a/internal/sc_realid/types.go b/internal/sc_realid/types.go deleted file mode 100644 index 2f98ca4..0000000 --- a/internal/sc_realid/types.go +++ /dev/null @@ -1,13 +0,0 @@ -package screalid - -type KycPassportResponse struct { - Status string `json:"status"` - FinalResult struct { - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - DateOfExpire string `json:"dateOfExpire"` - Nationality string `json:"nationality"` - DocumentType string `json:"documentType"` - DocumentNumber string `json:"documentNumber"` - } `json:"finalResult"` -}