main
go 87 lines 2.47 KB
Raw
1 package auth
2
3 import (
4 "errors"
5 "fmt"
6 "strings"
7 "time"
8
9 "github.com/spruceid/siwe-go"
10
11 "github.com/gosuda/portal-tunnel/v2/portal/identity"
12 "github.com/gosuda/portal-tunnel/v2/types"
13 "github.com/gosuda/portal-tunnel/v2/utils"
14 )
15
16 var (
17 ErrRegisterChallengeExpired = errors.New("register challenge expired")
18 ErrRegisterChallengeNotFound = errors.New("register challenge not found")
19 ErrRegisterChallengeInvalidSignature = errors.New("siwe signature is invalid")
20 )
21
22 type RegisterChallenge struct {
23 ChallengeID string
24 ExpiresAt time.Time
25 Request types.RegisterChallengeRequest
26 SIWEMessage string
27
28 domain string
29 nonce string
30 }
31
32 func NewRegisterChallenge(req types.RegisterChallengeRequest, domain, uri string, now time.Time, ttl time.Duration) (*RegisterChallenge, error) {
33 normalizedIdentity, err := identity.NormalizeIdentity(req.Identity)
34 if err != nil {
35 return nil, err
36 }
37
38 challengeID := utils.RandomID("rch_")
39 nonce := siwe.GenerateNonce()
40 expiresAt := now.UTC().Add(ttl)
41 message, err := siwe.InitMessage(domain, normalizedIdentity.Address, uri, nonce, map[string]interface{}{
42 "statement": "Register a portal lease",
43 "chainId": 1,
44 "issuedAt": now.UTC().Format(time.RFC3339),
45 "expirationTime": expiresAt.UTC().Format(time.RFC3339),
46 "requestId": challengeID,
47 })
48 if err != nil {
49 return nil, fmt.Errorf("build siwe message: %w", err)
50 }
51
52 req.Identity = normalizedIdentity
53 req.Metadata = req.Metadata.Copy()
54
55 return &RegisterChallenge{
56 ChallengeID: challengeID,
57 ExpiresAt: expiresAt,
58 Request: req,
59 SIWEMessage: message.String(),
60 domain: strings.TrimSpace(domain),
61 nonce: nonce,
62 }, nil
63 }
64
65 func (c *RegisterChallenge) Expired(now time.Time) bool {
66 return c == nil || now.After(c.ExpiresAt)
67 }
68
69 func (c *RegisterChallenge) Verify(req types.RegisterRequest, now time.Time) error {
70 if c == nil {
71 return ErrRegisterChallengeNotFound
72 }
73 if strings.TrimSpace(req.SIWEMessage) != c.SIWEMessage {
74 return errors.New("siwe message does not match register challenge")
75 }
76 message, err := siwe.ParseMessage(strings.TrimSpace(c.SIWEMessage))
77 if err != nil {
78 return ErrRegisterChallengeInvalidSignature
79 }
80 normalizedDomain := strings.TrimSpace(c.domain)
81 normalizedNonce := strings.TrimSpace(c.nonce)
82 verifiedAt := now.UTC()
83 if _, err := message.Verify(strings.TrimSpace(req.SIWESignature), &normalizedDomain, &normalizedNonce, &verifiedAt); err != nil {
84 return ErrRegisterChallengeInvalidSignature
85 }
86 return nil
87 }