WIP: Full restruct, seprate service and api
Signed-off-by: Asai Neko <sugar@sne.moe>
This commit is contained in:
168
internal/ali_cnrid/kyc.go
Normal file
168
internal/ali_cnrid/kyc.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package kyc
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"nixcn-cms/internal/cryptography"
|
||||
"unicode/utf8"
|
||||
|
||||
alicloudauth20190307 "github.com/alibabacloud-go/cloudauth-20190307/v4/client"
|
||||
aliopenapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
aliutil "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
alitea "github.com/alibabacloud-go/tea/tea"
|
||||
alicredential "github.com/aliyun/credentials-go/credentials"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func DecodeB64Json(b64Json string) (*KycInfo, error) {
|
||||
rawJson, err := base64.StdEncoding.DecodeString(b64Json)
|
||||
if err != nil {
|
||||
return nil, errors.New("[KYC] invalid base64 json")
|
||||
}
|
||||
|
||||
var kyc KycInfo
|
||||
if err := json.Unmarshal(rawJson, &kyc); err != nil {
|
||||
return nil, errors.New("[KYC] invalid json structure")
|
||||
}
|
||||
|
||||
return &kyc, nil
|
||||
}
|
||||
|
||||
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 kyc KycInfo
|
||||
if err := json.Unmarshal(plainBytes, &kyc); err != nil {
|
||||
return nil, errors.New("[KYC] invalid decrypted json")
|
||||
}
|
||||
|
||||
return &kyc, nil
|
||||
}
|
||||
|
||||
func MD5AliEnc(kyc *KycInfo) (*KycAli, 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 {
|
||||
return nil, fmt.Errorf("input string must have at least 2 Chinese characters")
|
||||
}
|
||||
|
||||
lnFirstRune, size := utf8.DecodeRuneInString(kyc.LegalName)
|
||||
if lnFirstRune == utf8.RuneError {
|
||||
return nil, fmt.Errorf("invalid first character")
|
||||
}
|
||||
|
||||
lnHash := md5.New()
|
||||
lnHash.Write([]byte(string(lnFirstRune)))
|
||||
lnFirstHash := hex.EncodeToString(lnHash.Sum(nil))
|
||||
|
||||
lnRemaining := kyc.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 {
|
||||
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]
|
||||
|
||||
ridHash := md5.New()
|
||||
ridHash.Write([]byte(ridMiddle))
|
||||
ridMiddleHash := hex.EncodeToString(ridHash.Sum(nil))
|
||||
|
||||
rid := ridPrefix + ridMiddleHash + ridSuffix
|
||||
|
||||
// Aliyun Id2MetaVerify API Params
|
||||
var kycAli KycAli
|
||||
kycAli.ParamType = "md5"
|
||||
kycAli.UserName = ln
|
||||
kycAli.IdentifyNum = rid
|
||||
|
||||
return &kycAli, nil
|
||||
}
|
||||
|
||||
func AliId2MetaVerify(kycAli *KycAli) (*string, error) {
|
||||
// Create aliyun openapi credential
|
||||
credentialConfig := new(alicredential.Config).
|
||||
SetType("access_key").
|
||||
SetAccessKeyId(viper.GetString("kyc.ali_access_key_id")).
|
||||
SetAccessKeySecret(viper.GetString("kyc.ali_access_key_secret"))
|
||||
credential, err := alicredential.NewCredential(credentialConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create aliyun cloudauth client
|
||||
config := &aliopenapi.Config{
|
||||
Credential: credential,
|
||||
}
|
||||
config.Endpoint = alitea.String("cloudauth.aliyuncs.com")
|
||||
client := &alicloudauth20190307.Client{}
|
||||
client, err = alicloudauth20190307.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create Id2MetaVerify request
|
||||
id2MetaVerifyRequest := &alicloudauth20190307.Id2MetaVerifyRequest{
|
||||
ParamType: &kycAli.ParamType,
|
||||
UserName: &kycAli.UserName,
|
||||
IdentifyNum: &kycAli.IdentifyNum,
|
||||
}
|
||||
|
||||
// Create client runtime request
|
||||
runtime := &aliutil.RuntimeOptions{}
|
||||
resp, tryErr := func() (*alicloudauth20190307.Id2MetaVerifyResponse, error) {
|
||||
defer func() {
|
||||
if r := alitea.Recover(recover()); r != nil {
|
||||
err = r
|
||||
}
|
||||
}()
|
||||
resp, err := client.Id2MetaVerifyWithOptions(id2MetaVerifyRequest, runtime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}()
|
||||
|
||||
// Try error handler ??? from ali generated sdk
|
||||
if tryErr != nil {
|
||||
var error = &alitea.SDKError{}
|
||||
if t, ok := tryErr.(*alitea.SDKError); ok {
|
||||
error = t
|
||||
} else {
|
||||
error.Message = alitea.String(tryErr.Error())
|
||||
}
|
||||
return nil, error
|
||||
}
|
||||
|
||||
return resp.Body.ResultObject.BizCode, err
|
||||
}
|
||||
7
internal/ali_cnrid/types.go
Normal file
7
internal/ali_cnrid/types.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package kyc
|
||||
|
||||
type KycAli struct {
|
||||
ParamType string `json:"param_type"`
|
||||
IdentifyNum string `json:"identify_num"`
|
||||
UserName string `json:"user_name"`
|
||||
}
|
||||
65
internal/authcode/authcode.go
Normal file
65
internal/authcode/authcode.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package authcode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"nixcn-cms/data"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
ClientId string
|
||||
Email string
|
||||
}
|
||||
|
||||
func NewAuthCode(ctx context.Context, clientId string, email string) (string, error) {
|
||||
// generate random code
|
||||
b := make([]byte, 32)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
code := base64.RawURLEncoding.EncodeToString(b)
|
||||
key := "auth_code:" + code
|
||||
|
||||
ttl := viper.GetDuration("ttl.auth_code_ttl")
|
||||
|
||||
// store auth code metadata in Redis
|
||||
if err := data.Redis.HSet(
|
||||
ctx,
|
||||
key,
|
||||
map[string]any{
|
||||
"client_id": clientId,
|
||||
"email": email,
|
||||
},
|
||||
).Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// set expiration (one-time auth code)
|
||||
if err := data.Redis.Expire(ctx, key, ttl).Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return code, nil
|
||||
}
|
||||
|
||||
func VerifyAuthCode(ctx context.Context, code string) (*Token, bool) {
|
||||
key := "auth_code:" + code
|
||||
|
||||
// Read auth code payload
|
||||
dataMap, err := data.Redis.HGetAll(ctx, key).Result()
|
||||
if err != nil || len(dataMap) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Delete auth code immediately (one-time use)
|
||||
_ = data.Redis.Del(ctx, key).Err()
|
||||
|
||||
return &Token{
|
||||
ClientId: dataMap["client_id"],
|
||||
Email: dataMap["email"],
|
||||
}, true
|
||||
}
|
||||
291
internal/authtoken/authtoken.go
Normal file
291
internal/authtoken/authtoken.go
Normal file
@@ -0,0 +1,291 @@
|
||||
package authtoken
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"nixcn-cms/data"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Application string
|
||||
}
|
||||
|
||||
type JwtClaims struct {
|
||||
ClientId string `json:"client_id"`
|
||||
UserID uuid.UUID `json:"user_id"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// Generate jwt clames
|
||||
func (self *Token) NewClaims(clientId string, userId uuid.UUID) JwtClaims {
|
||||
return JwtClaims{
|
||||
ClientId: clientId,
|
||||
UserID: userId,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(viper.GetDuration("ttl.access_ttl"))),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
Issuer: self.Application,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Generate access token
|
||||
func (self *Token) GenerateAccessToken(ctx context.Context, clientId string, userId uuid.UUID) (string, error) {
|
||||
claims := self.NewClaims(clientId, userId)
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
clientData, err := new(data.Client).GetClientByClientId(ctx, clientId)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error getting client data: %v", err)
|
||||
}
|
||||
|
||||
secret, err := clientData.GetDecryptedSecret()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error getting decrypted secret: %v", err)
|
||||
}
|
||||
|
||||
signedToken, err := token.SignedString([]byte(secret))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error signing token: %v", err)
|
||||
}
|
||||
return signedToken, nil
|
||||
}
|
||||
|
||||
// Generate refresh token
|
||||
func (self *Token) GenerateRefreshToken() (string, error) {
|
||||
b := make([]byte, 32)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// Issue both access and refresh token
|
||||
func (self *Token) IssueTokens(ctx context.Context, clientId string, userId uuid.UUID) (string, string, error) {
|
||||
// access token
|
||||
access, err := self.GenerateAccessToken(ctx, clientId, userId)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// refresh token
|
||||
refresh, err := self.GenerateRefreshToken()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
ttl := viper.GetDuration("ttl.refresh_ttl")
|
||||
|
||||
refreshKey := "refresh:" + refresh
|
||||
|
||||
// refresh -> user + client
|
||||
if err := data.Redis.HSet(
|
||||
ctx,
|
||||
refreshKey,
|
||||
map[string]any{
|
||||
"user_id": userId.String(),
|
||||
"client_id": clientId,
|
||||
},
|
||||
).Err(); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if err := data.Redis.Expire(ctx, refreshKey, ttl).Err(); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// user -> refresh tokens
|
||||
userSetKey := "user:" + userId.String() + ":refresh_tokens"
|
||||
|
||||
if err := data.Redis.SAdd(ctx, userSetKey, refresh).Err(); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
_ = data.Redis.Expire(ctx, userSetKey, ttl).Err()
|
||||
|
||||
// client -> refresh tokens
|
||||
clientSetKey := "client:" + clientId + ":refresh_tokens"
|
||||
_ = data.Redis.SAdd(ctx, clientSetKey, refresh).Err()
|
||||
_ = data.Redis.Expire(ctx, clientSetKey, ttl).Err()
|
||||
|
||||
return access, refresh, nil
|
||||
}
|
||||
|
||||
// Refresh access token
|
||||
func (self *Token) RefreshAccessToken(ctx context.Context, refreshToken string) (string, error) {
|
||||
key := "refresh:" + refreshToken
|
||||
|
||||
// read refresh token bind data
|
||||
dataMap, err := data.Redis.HGetAll(ctx, key).Result()
|
||||
if err != nil || len(dataMap) == 0 {
|
||||
return "", errors.New("[Auth Token] invalid refresh token")
|
||||
}
|
||||
|
||||
userIdStr := dataMap["user_id"]
|
||||
clientId := dataMap["client_id"]
|
||||
|
||||
if userIdStr == "" || clientId == "" {
|
||||
return "", errors.New("[Auth Token] refresh token corrupted")
|
||||
}
|
||||
|
||||
userId, err := uuid.Parse(userIdStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Generate new access token
|
||||
return self.GenerateAccessToken(ctx, clientId, userId)
|
||||
}
|
||||
|
||||
func (self *Token) RenewRefreshToken(ctx context.Context, refreshToken string) (string, error) {
|
||||
ttl := viper.GetDuration("ttl.refresh_ttl")
|
||||
|
||||
oldKey := "refresh:" + refreshToken
|
||||
|
||||
// read old refresh token bind data
|
||||
dataMap, err := data.Redis.HGetAll(ctx, oldKey).Result()
|
||||
if err != nil || len(dataMap) == 0 {
|
||||
return "", errors.New("[Auth Token] invalid refresh token")
|
||||
}
|
||||
|
||||
userIdStr := dataMap["user_id"]
|
||||
clientId := dataMap["client_id"]
|
||||
|
||||
if userIdStr == "" || clientId == "" {
|
||||
return "", errors.New("[Auth Token] refresh token corrupted")
|
||||
}
|
||||
|
||||
// generate new refresh token
|
||||
newRefresh, err := self.GenerateRefreshToken()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// revoke old refresh token
|
||||
if err := self.RevokeRefreshToken(ctx, refreshToken); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
newKey := "refresh:" + newRefresh
|
||||
|
||||
// refresh -> user + client
|
||||
if err := data.Redis.HSet(
|
||||
ctx,
|
||||
newKey,
|
||||
map[string]any{
|
||||
"user_id": userIdStr,
|
||||
"client_id": clientId,
|
||||
},
|
||||
).Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := data.Redis.Expire(ctx, newKey, ttl).Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// user -> refresh tokens
|
||||
userSetKey := "user:" + userIdStr + ":refresh_tokens"
|
||||
if err := data.Redis.SAdd(ctx, userSetKey, newRefresh).Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
_ = data.Redis.Expire(ctx, userSetKey, ttl).Err()
|
||||
|
||||
// client -> refresh tokens
|
||||
clientSetKey := "client:" + clientId + ":refresh_tokens"
|
||||
_ = data.Redis.SAdd(ctx, clientSetKey, newRefresh).Err()
|
||||
_ = data.Redis.Expire(ctx, clientSetKey, ttl).Err()
|
||||
|
||||
return newRefresh, nil
|
||||
}
|
||||
|
||||
func (self *Token) RevokeRefreshToken(ctx context.Context, refreshToken string) error {
|
||||
refreshKey := "refresh:" + refreshToken
|
||||
|
||||
// read refresh token metadata (user_id, client_id)
|
||||
dataMap, err := data.Redis.HGetAll(ctx, refreshKey).Result()
|
||||
if err != nil || len(dataMap) == 0 {
|
||||
// Token already revoked or not found
|
||||
return nil
|
||||
}
|
||||
|
||||
userID := dataMap["user_id"]
|
||||
clientID := dataMap["client_id"]
|
||||
|
||||
// build index keys
|
||||
userSetKey := "user:" + userID + ":refresh_tokens"
|
||||
clientSetKey := "client:" + clientID + ":refresh_tokens"
|
||||
|
||||
// remove refresh token and all related indexes atomically
|
||||
pipe := data.Redis.TxPipeline()
|
||||
|
||||
// remove main refresh token record
|
||||
pipe.Del(ctx, refreshKey)
|
||||
|
||||
// remove refresh token from user's active refresh token set
|
||||
pipe.SRem(ctx, userSetKey, refreshToken)
|
||||
|
||||
// remove refresh token from client's active refresh token set
|
||||
pipe.SRem(ctx, clientSetKey, refreshToken)
|
||||
|
||||
_, err = pipe.Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *Token) HeaderVerify(ctx context.Context, header string) (string, error) {
|
||||
if header == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Split header to 2
|
||||
parts := strings.SplitN(header, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return "", errors.New("[Auth Token] invalid Authorization header format")
|
||||
}
|
||||
|
||||
tokenStr := parts[1]
|
||||
|
||||
// Verify access token
|
||||
claims := &JwtClaims{}
|
||||
token, err := jwt.ParseWithClaims(
|
||||
tokenStr,
|
||||
claims,
|
||||
func(token *jwt.Token) (any, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errors.New("[Auth Token] unexpected signing method")
|
||||
}
|
||||
|
||||
if claims.ClientId == "" {
|
||||
return nil, errors.New("[Auth Token] client_id missing in token")
|
||||
}
|
||||
|
||||
clientData, err := new(data.Client).GetClientByClientId(ctx, claims.ClientId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting client data: %v", err)
|
||||
}
|
||||
|
||||
secret, err := clientData.GetDecryptedSecret()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting decrypted secret: %v", err)
|
||||
}
|
||||
|
||||
return secret, nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil || !token.Valid {
|
||||
fmt.Println(err)
|
||||
return "", errors.New("[Auth Token] invalid or expired token")
|
||||
}
|
||||
|
||||
return claims.UserID.String(), nil
|
||||
}
|
||||
84
internal/email/email.go
Normal file
84
internal/email/email.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
gomail "gopkg.in/gomail.v2"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
dialer *gomail.Dialer
|
||||
|
||||
host string
|
||||
port int
|
||||
username string
|
||||
security string
|
||||
insecure bool
|
||||
}
|
||||
|
||||
func (self *Client) NewSMTPClient() (*Client, error) {
|
||||
host := viper.GetString("email.host")
|
||||
port := viper.GetInt("email.port")
|
||||
user := viper.GetString("email.username")
|
||||
pass := viper.GetString("email.password")
|
||||
|
||||
security := strings.ToLower(viper.GetString("email.security"))
|
||||
insecure := viper.GetBool("email.insecure_skip_verify")
|
||||
|
||||
if host == "" || port == 0 || user == "" {
|
||||
return nil, errors.New("[Email] SMTP config not set")
|
||||
}
|
||||
|
||||
if pass == "" {
|
||||
return nil, errors.New("[Email] SMTP basic auth requires email.password")
|
||||
}
|
||||
|
||||
dialer := gomail.NewDialer(host, port, user, pass)
|
||||
dialer.TLSConfig = &tls.Config{
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: insecure,
|
||||
}
|
||||
|
||||
switch security {
|
||||
case "ssl":
|
||||
dialer.SSL = true
|
||||
case "starttls":
|
||||
dialer.SSL = false
|
||||
case "plain", "":
|
||||
dialer.SSL = false
|
||||
dialer.TLSConfig = nil
|
||||
default:
|
||||
return nil, errors.New("[Email] unknown smtp security mode: " + security)
|
||||
}
|
||||
|
||||
return &Client{
|
||||
dialer: dialer,
|
||||
host: host,
|
||||
port: port,
|
||||
username: user,
|
||||
security: security,
|
||||
insecure: insecure,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) Send(from, to, subject, html string) (string, error) {
|
||||
if c.dialer == nil {
|
||||
return "", errors.New("[Email] SMTP dialer not initialized")
|
||||
}
|
||||
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", from)
|
||||
m.SetHeader("To", to)
|
||||
m.SetHeader("Subject", subject)
|
||||
m.SetBody("text/html", html)
|
||||
|
||||
if err := c.dialer.DialAndSend(m); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return time.Now().Format(time.RFC3339Nano), nil
|
||||
}
|
||||
@@ -13,12 +13,13 @@ import (
|
||||
// :5=original
|
||||
|
||||
type Builder struct {
|
||||
Status string
|
||||
Service string
|
||||
Endpoint string
|
||||
Type string
|
||||
Original string
|
||||
Error error
|
||||
Status string
|
||||
Service string
|
||||
Endpoint string
|
||||
Type string
|
||||
Original string
|
||||
Error error
|
||||
ErrorCode string
|
||||
}
|
||||
|
||||
func (self *Builder) SetStatus(s string) *Builder {
|
||||
@@ -51,16 +52,25 @@ func (self *Builder) SetError(e error) *Builder {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *Builder) Build(ctx context.Context) string {
|
||||
errorCode := fmt.Sprintf("%s%s%s%s%s",
|
||||
func (self *Builder) build() {
|
||||
self.ErrorCode = fmt.Sprintf("%s%s%s%s%s",
|
||||
self.Status,
|
||||
self.Service,
|
||||
self.Endpoint,
|
||||
self.Type,
|
||||
self.Original,
|
||||
)
|
||||
if self.Error != nil {
|
||||
ErrorHandler(ctx, self.Status, errorCode, self.Error)
|
||||
}
|
||||
return errorCode
|
||||
}
|
||||
|
||||
func (self *Builder) Throw(ctx *context.Context) *Builder {
|
||||
self.build()
|
||||
if self.Error != nil {
|
||||
ErrorHandler(ctx, self.Status, self.ErrorCode, self.Error)
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *Builder) String() string {
|
||||
self.build()
|
||||
return self.ErrorCode
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@ import (
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
func ErrorHandler(ctx context.Context, status string, errorCode string, err error) {
|
||||
func ErrorHandler(ctx *context.Context, status string, errorCode string, err error) {
|
||||
switch status {
|
||||
case StatusSuccess:
|
||||
slog.InfoContext(ctx, "Service exception! ErrId: "+errorCode, "id", errorCode, "err", err)
|
||||
slog.InfoContext(*ctx, "Service exception! ErrId: "+errorCode, "id", errorCode, "err", err)
|
||||
case StatusUser:
|
||||
slog.WarnContext(ctx, "Service exception! ErrId: "+errorCode, "id", errorCode, "err", err)
|
||||
slog.WarnContext(*ctx, "Service exception! ErrId: "+errorCode, "id", errorCode, "err", err)
|
||||
case StatusServer:
|
||||
slog.ErrorContext(ctx, "Service exception! ErrId: "+errorCode, "id", errorCode, "err", err)
|
||||
slog.ErrorContext(*ctx, "Service exception! ErrId: "+errorCode, "id", errorCode, "err", err)
|
||||
case StatusClient:
|
||||
slog.ErrorContext(ctx, "Service exception! ErrId: "+errorCode, "id", errorCode, "err", err)
|
||||
slog.ErrorContext(*ctx, "Service exception! ErrId: "+errorCode, "id", errorCode, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
17
internal/kyc/types.go
Normal file
17
internal/kyc/types.go
Normal file
@@ -0,0 +1,17 @@
|
||||
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 PassportInfo struct {
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
DateOfExpire string `json:"date_of_expire"`
|
||||
Nationality string `json:"nationality"`
|
||||
DocumentType string `json:"document_type"`
|
||||
DocumentNumber string `json:"document_number"`
|
||||
}
|
||||
13
internal/sc_realid/types.go
Normal file
13
internal/sc_realid/types.go
Normal file
@@ -0,0 +1,13 @@
|
||||
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"`
|
||||
}
|
||||
34
internal/turnstile/turnstile.go
Normal file
34
internal/turnstile/turnstile.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package turnstile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func VerifyTurnstile(token, ip string) (bool, error) {
|
||||
form := url.Values{}
|
||||
form.Set("secret", viper.GetString("secrets.turnstile_secret"))
|
||||
form.Set("response", token)
|
||||
form.Set("remoteip", ip)
|
||||
|
||||
resp, err := http.PostForm(
|
||||
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
|
||||
form,
|
||||
)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return result.Success, nil
|
||||
}
|
||||
Reference in New Issue
Block a user