Skip to content

Commit a7b53e1

Browse files
committed
refactor, implement link sharing/revoking
1 parent 08cf980 commit a7b53e1

File tree

8 files changed

+400
-310
lines changed

8 files changed

+400
-310
lines changed

auth/middleware.go

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package auth
2+
3+
import (
4+
"context"
5+
"net/http"
6+
7+
"github.com/charmbracelet/log"
8+
"github.com/golang-jwt/jwt/v5"
9+
)
10+
11+
type ctxUserKey struct{}
12+
13+
func GetRequestUsername(r *http.Request) string {
14+
if claims, ok := r.Context().Value(ctxUserKey{}).(string); ok {
15+
return claims
16+
}
17+
return ""
18+
}
19+
20+
func AdminOnly(next http.Handler) http.Handler {
21+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22+
if username := GetRequestUsername(r); username != "admin" {
23+
http.Error(w, "Forbidden", http.StatusForbidden)
24+
return
25+
}
26+
next.ServeHTTP(w, r)
27+
})
28+
}
29+
30+
func Middleware(hmacSecret []byte, next http.Handler) http.Handler {
31+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
32+
// Check for the presence of the Authorization header
33+
authHeader := r.Header.Get("Authorization")
34+
var tokenStr string
35+
if authHeader == "" {
36+
// try getting from query
37+
tokenStr = r.URL.Query().Get("token")
38+
} else {
39+
// Split the header into "Bearer" and the token
40+
tokenStr = authHeader[len("Bearer "):]
41+
}
42+
43+
if tokenStr == "" {
44+
http.Error(w, "Unauthorized", http.StatusUnauthorized)
45+
return
46+
}
47+
48+
// Check if the token is valid
49+
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
50+
return hmacSecret, nil
51+
}, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}))
52+
if err != nil {
53+
log.Errorf("Error parsing token: %v", err)
54+
http.Error(w, "Unauthorized", http.StatusUnauthorized)
55+
return
56+
}
57+
58+
if claims, err := token.Claims.GetSubject(); err != nil {
59+
log.Errorf("Error parsing token: %v", err)
60+
http.Error(w, "Unauthorized", http.StatusUnauthorized)
61+
return
62+
} else {
63+
// Store the claims in the request context
64+
ctx := context.WithValue(r.Context(), ctxUserKey{}, claims)
65+
next.ServeHTTP(w, r.WithContext(ctx))
66+
}
67+
})
68+
}

auth/tokens.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package auth
2+
3+
import (
4+
"github.com/golang-jwt/jwt/v5"
5+
"time"
6+
)
7+
8+
func GenerateAccessToken(hmacSecret []byte, username string, expiration time.Time) (string, error) {
9+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
10+
"sub": username,
11+
"exp": expiration.Unix(), // Set the expiration time
12+
})
13+
14+
// Sign and get the complete encoded token as a string using the secret
15+
return token.SignedString(hmacSecret)
16+
}

cmd/cmd_init.go

+16-227
Original file line numberDiff line numberDiff line change
@@ -1,234 +1,17 @@
11
package main
22

33
import (
4-
"bytes"
5-
"context"
6-
"encoding/base64"
7-
"encoding/json"
8-
"fmt"
9-
"github.com/mdp/qrterminal/v3"
10-
box "github.com/sagernet/sing-box"
11-
"github.com/sagernet/sing-box/include"
4+
"github.com/getlantern/lantern-server-manager/common"
125
"io"
13-
"math/rand/v2"
146
"net/http"
15-
"net/netip"
16-
"os"
177
"strings"
188

199
"github.com/charmbracelet/log"
20-
"github.com/golang-jwt/jwt/v5"
21-
"github.com/sagernet/sing-box/option"
22-
"github.com/sagernet/sing/common"
23-
singJson "github.com/sagernet/sing/common/json"
24-
"github.com/sagernet/sing/common/json/badjson"
25-
"github.com/sagernet/sing/common/json/badoption"
2610
)
2711

2812
type InitCmd struct {
2913
}
3014

31-
func GenerateAccessToken(hmacSecret []byte) (string, error) {
32-
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
33-
"sub": "admin",
34-
// no expiration
35-
})
36-
37-
// Sign and get the complete encoded token as a string using the secret
38-
return token.SignedString(hmacSecret)
39-
}
40-
41-
func PasswordGenerator(passwordLength int) string {
42-
lowerCase := "abcdefghijklmnopqrstuvwxyz" // lowercase
43-
upperCase := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" // uppercase
44-
Numbers := "0123456789" // numbers
45-
specialChar := "!@#$%^&*()_-+={}[/?]" // specialchar
46-
47-
// variable for storing password
48-
password := ""
49-
50-
for n := 0; n < passwordLength; n++ {
51-
// NOW RANDOM CHARACTER
52-
randNum := rand.N(4)
53-
54-
switch randNum {
55-
case 0:
56-
randCharNum := rand.N(len(lowerCase))
57-
password += string(lowerCase[randCharNum])
58-
case 1:
59-
randCharNum := rand.N(len(upperCase))
60-
password += string(upperCase[randCharNum])
61-
case 2:
62-
randCharNum := rand.N(len(Numbers))
63-
password += string(Numbers[randCharNum])
64-
case 3:
65-
randCharNum := rand.N(len(specialChar))
66-
password += string(specialChar[randCharNum])
67-
}
68-
}
69-
70-
return password
71-
}
72-
73-
type ServerConfig struct {
74-
Port int `json:"port"`
75-
AccessToken string `json:"access_token"`
76-
HMACSecret string `json:"hmac_secret"`
77-
}
78-
79-
func (c *ServerConfig) GetNewServerURL(publicIP string) string {
80-
return fmt.Sprintf("lantern://new-private-server?ip=%s&port=%d&token=%s", publicIP, c.Port, c.AccessToken)
81-
}
82-
func (c *ServerConfig) GetQR(publicIP string) string {
83-
qrCode := bytes.NewBufferString("")
84-
qrterminal.Generate(c.GetNewServerURL(publicIP), qrterminal.L, qrCode)
85-
86-
return qrCode.String()
87-
}
88-
func readServerConfig() (*ServerConfig, error) {
89-
data, err := os.ReadFile("server.json")
90-
if err != nil {
91-
return nil, err
92-
}
93-
var config ServerConfig
94-
if err := json.Unmarshal(data, &config); err != nil {
95-
return nil, err
96-
}
97-
if config.Port == 0 {
98-
return nil, fmt.Errorf("port not set")
99-
}
100-
if config.AccessToken == "" {
101-
return nil, fmt.Errorf("access token not set")
102-
}
103-
if config.HMACSecret == "" {
104-
return nil, fmt.Errorf("hmac secret not set")
105-
}
106-
return &config, nil
107-
}
108-
109-
func generateServerConfig() (*ServerConfig, error) {
110-
// generate a number that is a valid non-privileged port
111-
port := rand.N(65535-1024) + 1024
112-
// generate hmac secret
113-
hmacSecret := PasswordGenerator(32)
114-
// generate an access token
115-
accessToken, err := GenerateAccessToken([]byte(hmacSecret))
116-
if err != nil {
117-
return nil, err
118-
}
119-
conf := &ServerConfig{
120-
Port: port,
121-
AccessToken: accessToken,
122-
HMACSecret: hmacSecret,
123-
}
124-
// write the config to a file
125-
data, err := json.Marshal(conf)
126-
if err != nil {
127-
return nil, err
128-
}
129-
log.Infof("Writing intial config to server.json")
130-
return conf, os.WriteFile("server.json", data, 0600)
131-
}
132-
133-
func readSingBoxServerConfig() (*option.Options, error) {
134-
data, err := os.ReadFile("sing-box-config.json")
135-
if err != nil {
136-
return nil, err
137-
}
138-
globalCtx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry())
139-
140-
opt, err := singJson.UnmarshalExtendedContext[option.Options](globalCtx, data)
141-
if err != nil {
142-
return nil, err
143-
}
144-
return &opt, nil
145-
}
146-
147-
func generateSingboxConnectConfig(publicIP, username string) ([]byte, error) {
148-
singBoxServerConfig, err := readSingBoxServerConfig()
149-
if err != nil {
150-
return nil, err
151-
}
152-
if len(singBoxServerConfig.Inbounds) == 0 {
153-
return nil, fmt.Errorf("no inbounds found, invalid config")
154-
}
155-
inboundOptions, ok := singBoxServerConfig.Inbounds[0].Options.(*option.ShadowsocksInboundOptions)
156-
if !ok {
157-
return nil, fmt.Errorf("inbound is not shadowsocks")
158-
}
159-
var password string
160-
if username == "admin" {
161-
password = inboundOptions.Password
162-
} else {
163-
for _, u := range inboundOptions.Users {
164-
if u.Name == username {
165-
password = u.Password
166-
break
167-
}
168-
}
169-
}
170-
if password == "" {
171-
return nil, fmt.Errorf("user not found")
172-
}
173-
opt := option.Options{
174-
Log: &option.LogOptions{
175-
Level: "debug",
176-
Output: "stdout",
177-
},
178-
Outbounds: []option.Outbound{
179-
{
180-
Type: "shadowsocks",
181-
Tag: "ss-outbound",
182-
Options: &option.ShadowsocksOutboundOptions{
183-
DialerOptions: option.DialerOptions{},
184-
ServerOptions: option.ServerOptions{
185-
Server: publicIP,
186-
ServerPort: inboundOptions.ListenPort,
187-
},
188-
Method: "2022-blake3-aes-128-gcm",
189-
Password: password,
190-
},
191-
},
192-
},
193-
}
194-
return badjson.MarshallObjects(opt)
195-
}
196-
197-
func generateBasicSingboxServerConfig() error {
198-
// generate a number that is a valid non-privileged port
199-
port := rand.N(65535-1024) + 1024
200-
// generate a password. we are using 2022-blake3-aes-128-gcm so length must be 16
201-
password := base64.StdEncoding.EncodeToString([]byte(PasswordGenerator(16)))
202-
// generate basic shadowsocks config
203-
opt := option.Options{
204-
Log: &option.LogOptions{
205-
Level: "debug",
206-
Output: "stdout",
207-
},
208-
Inbounds: []option.Inbound{
209-
{
210-
Type: "shadowsocks",
211-
Tag: "ss-inbound",
212-
213-
Options: &option.ShadowsocksInboundOptions{
214-
Method: "2022-blake3-aes-128-gcm",
215-
ListenOptions: option.ListenOptions{
216-
ListenPort: uint16(port),
217-
Listen: common.Ptr(badoption.Addr(netip.AddrFrom4([4]byte{0, 0, 0, 0}))),
218-
},
219-
Password: password,
220-
},
221-
},
222-
},
223-
}
224-
data, err := badjson.MarshallObjects(opt)
225-
if err != nil {
226-
return err
227-
}
228-
log.Infof("Writing intial vpn config to sing-box-config.json")
229-
return os.WriteFile("sing-box-config.json", data, 0644)
230-
}
231-
23215
func getPublicIP() (string, error) {
23316
resp, err := http.Get("https://ifconfig.io")
23417
if err != nil {
@@ -244,22 +27,28 @@ func getPublicIP() (string, error) {
24427
return strings.TrimSpace(string(body)), nil
24528
}
24629

247-
func (c InitCmd) Run() error {
248-
var err error
249-
var config *ServerConfig
250-
if config, err = generateServerConfig(); err != nil {
251-
return err
30+
func InitializeConfigs() (*common.ServerConfig, error) {
31+
config, err := common.GenerateServerConfig()
32+
if err != nil {
33+
return nil, err
25234
}
253-
if err = generateBasicSingboxServerConfig(); err != nil {
254-
return err
35+
if err = common.GenerateBasicSingboxServerConfig(); err != nil {
36+
return nil, err
25537
}
38+
return config, nil
39+
}
25640

257-
printRootToken(config)
41+
func (c InitCmd) Run() error {
42+
if config, err := InitializeConfigs(); err != nil {
43+
return err
44+
} else {
45+
printRootToken(config)
46+
}
25847

25948
return nil
26049
}
26150

262-
func printRootToken(config *ServerConfig) {
51+
func printRootToken(config *common.ServerConfig) {
26352
publicIP, err := getPublicIP()
26453
if err != nil {
26554
log.Error("Cannot detect your public IP, please get if from your host provider")

0 commit comments

Comments
 (0)