-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbchauth.go
More file actions
257 lines (227 loc) · 7.16 KB
/
bchauth.go
File metadata and controls
257 lines (227 loc) · 7.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
package bchauth
import (
"database/sql"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/core-coin/go-core/v2/common"
"github.com/core-coin/go-core/v2/crypto"
"github.com/go-redis/redis/v8"
"golang.org/x/net/context"
_ "github.com/lib/pq" // PostgreSQL driver
)
func init() {
caddy.RegisterModule(BchAuth{})
}
type BchAuth struct {
DB *sql.DB
RedisClient *redis.Client
DestWallet string `json:"dest_wallet"`
MinFundsCTN float64 `json:"funds_ctn"` // CTN amount required for 1 day of access
PGConnString string `json:"pg_conn_string"`
ConfiguredTable string `json:"configured_table"` // Table name for transactions
RedisAddr string `json:"redis_addr"` // Redis address
Whitelist []string `json:"whitelist"` // Public key whitelist
NetworkId int64 `json:"network_id"` // Network ID for blockchain addresses
}
// CaddyModule returns the Caddy module information.
func (BchAuth) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.handlers.bchauth",
New: func() caddy.Module { return new(BchAuth) },
}
}
// Provision initializes the PostgreSQL and Redis connections.
func (bch *BchAuth) Provision(ctx caddy.Context) error {
var err error
// Initialize PostgreSQL connection
bch.DB, err = sql.Open("postgres", bch.PGConnString)
if err != nil {
return fmt.Errorf("failed to connect to PostgreSQL: %v", err)
}
// Test the connection
if err := bch.DB.Ping(); err != nil {
return fmt.Errorf("failed to ping PostgreSQL: %v", err)
}
// Initialize Redis connection
bch.RedisClient = redis.NewClient(&redis.Options{
Addr: bch.RedisAddr,
})
if _, err := bch.RedisClient.Ping(ctx).Result(); err != nil {
return fmt.Errorf("failed to connect to Redis: %v", err)
}
return nil
}
// ServeHTTP verifies access based on blockchain transactions or whitelist.
func (bch *BchAuth) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
ctx := context.Background()
pubKey := r.Header.Get("X-Pub-Key")
if pubKey == "" {
http.Error(w, "Missing X-Pub-Key", http.StatusForbidden)
return nil
}
// Check whitelist
if bch.isWhitelisted(pubKey) {
return next.ServeHTTP(w, r)
}
// Check Redis cache
cacheKey := "access:" + pubKey
expiry, err := bch.RedisClient.Get(ctx, cacheKey).Result()
expiryInt, pasrseErr := strconv.ParseInt(expiry, 10, 64)
if pasrseErr != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return nil
}
if err == nil && time.Now().Before(time.Unix(0, 0).Add(time.Second*time.Duration(expiryInt))) {
return next.ServeHTTP(w, r)
}
// Generate wallet address using Ed448
address, err := bch.generateAddress(pubKey)
if err != nil {
http.Error(w, "Invalid Public Key", http.StatusForbidden)
return nil
}
// Query PostgreSQL to calculate active service days
activeDays, err := bch.checkActiveService(address, bch.MinFundsCTN)
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return nil
}
if activeDays <= 0 {
http.Error(w, "Service Expired", http.StatusForbidden)
return nil
}
// Set Redis cache for remaining valid service days
cacheDuration := int64(activeDays) * 86400 // Convert days to seconds
bch.RedisClient.Set(ctx, cacheKey, cacheDuration, time.Duration(cacheDuration)*time.Second)
return next.ServeHTTP(w, r)
}
// isWhitelisted checks if the public key is in the whitelist.
func (bch *BchAuth) isWhitelisted(pubKey string) bool {
for _, whitelistedKey := range bch.Whitelist {
if strings.EqualFold(whitelistedKey, pubKey) {
return true
}
}
return false
}
// checkActiveService queries the database for active service days based on the user's transactions.
func (bch *BchAuth) checkActiveService(address string, minFunds float64) (int, error) {
query := fmt.Sprintf(`
WITH RECURSIVE service_periods AS (
SELECT
t.created_at AS start_date,
t.created_at + INTERVAL '1 day' * FLOOR(t.value::NUMERIC / $2) AS end_date,
FLOOR(t.value::NUMERIC / $2) AS service_days
FROM %s t
WHERE t.to_addr = $1
UNION ALL
SELECT
CASE
WHEN t.created_at > sp.end_date THEN t.created_at
ELSE sp.start_date
END AS start_date,
t.created_at + INTERVAL '1 day' * FLOOR(t.value::NUMERIC / $2) AS end_date,
sp.service_days + FLOOR(t.value::NUMERIC / $2) AS service_days
FROM %s t
JOIN service_periods sp
ON t.to_addr = $1
AND t.created_at > sp.end_date
)
SELECT SUM(service_days)
FROM service_periods
WHERE start_date <= NOW() AND end_date >= NOW();
`, bch.ConfiguredTable, bch.ConfiguredTable)
var totalServiceDays int
err := bch.DB.QueryRow(query, address, minFunds).Scan(&totalServiceDays)
if err != nil {
return 0, err
}
return totalServiceDays, nil
}
// generateAddress derives the wallet address from the public key using Ed448.
func (bch *BchAuth) generateAddress(pubKey string) (string, error) {
pubKeyBytes := common.FromHex(pubKey)
if len(pubKeyBytes) != 57 {
return "", errors.New("invalid public key length")
}
addr := crypto.SHA3(pubKeyBytes[:])[12:]
checksum := common.Hex2Bytes(common.CalculateChecksum(addr, bch.NetworkIDPrefix()))
return common.BytesToAddress(append(append(bch.NetworkIDPrefix(), checksum...), addr...)).Hex(), nil
}
// UnmarshalCaddyfile sets up the module from Caddyfile.
func (bch *BchAuth) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
for d.NextBlock(0) {
switch d.Val() {
case "dest_wallet":
if !d.Args(&bch.DestWallet) {
return d.Err("expected value for dest_wallet")
}
case "funds_ctn":
var fundsCTNStr string
if !d.Args(&fundsCTNStr) {
return d.Err("expected value for funds_ctn")
}
fundsCTN, err := strconv.ParseFloat(fundsCTNStr, 64)
if err != nil {
return d.Err("invalid value for funds_ctn")
}
bch.MinFundsCTN = fundsCTN
case "pg_conn_string":
if !d.Args(&bch.PGConnString) {
return d.Err("expected PostgreSQL connection string")
}
case "configured_table":
if !d.Args(&bch.ConfiguredTable) {
return d.Err("expected configured table name")
}
case "redis_addr":
if !d.Args(&bch.RedisAddr) {
return d.Err("expected Redis address")
}
case "network_id":
var networkIdStr string
if !d.Args(&networkIdStr) {
return d.Err("expected network ID")
}
networkId, err := strconv.ParseInt(networkIdStr, 10, 64)
if err != nil {
return d.Err("invalid network ID format")
}
bch.NetworkId = networkId
case "whitelist":
args := d.RemainingArgs()
bch.Whitelist = args
}
}
}
return nil
}
// Cleanup closes the PostgreSQL connection.
func (bch *BchAuth) Cleanup() error {
if bch.DB != nil {
return bch.DB.Close()
}
return nil
}
func (bch *BchAuth) NetworkIDPrefix() []byte {
if bch.NetworkId == 1 {
return common.FromHex("cb")
} else if bch.NetworkId == 3 {
return common.FromHex("ab")
} else {
return common.FromHex("ce")
}
}
// Interface guards
var (
_ caddyhttp.MiddlewareHandler = (*BchAuth)(nil)
_ caddyfile.Unmarshaler = (*BchAuth)(nil)
)