Compare commits

...

9 Commits

Author SHA1 Message Date
413ad0629a 💄 Optimize meet page 2025-04-06 14:49:00 +08:00
aa3c0a9dbf 🐛 Fix meet room page missing channel 2025-04-06 14:41:25 +08:00
00ef003be9 Meet room rendering 2025-04-06 14:30:45 +08:00
e21ec2e81c 🐛 Fix wrong route for rate limiter 2025-03-30 22:58:02 +08:00
4350d197f9 DirectAccess Realm 2025-03-30 14:31:16 +08:00
d5422ab5b0 🐛 Fix nil rdl 2025-03-29 15:59:32 +08:00
4a08fd8f1c Revert to use cached userinfo for security reason 2025-03-29 15:27:29 +08:00
f113ae6cba User info DirectAccess™ 2025-03-29 15:21:30 +08:00
1ea5aea6b3 Prevent from creating multiple redis client 2025-03-29 15:11:07 +08:00
9 changed files with 161 additions and 5 deletions

1
.gitignore vendored
View File

@ -3,4 +3,5 @@
/default.etcd /default.etcd
/keys /keys
.idea
.DS_Store .DS_Store

View File

@ -1,8 +1,16 @@
package cache package cache
import "github.com/redis/go-redis/v9" import (
"time"
var Rdb *redis.Client "git.solsynth.dev/hypernet/nexus/pkg/nex/cachekit"
"github.com/redis/go-redis/v9"
)
var (
Rdb *redis.Client
Kcc *cachekit.Conn
)
func ConnectRedis(addr, password string, db int) error { func ConnectRedis(addr, password string, db int) error {
Rdb = redis.NewClient(&redis.Options{ Rdb = redis.NewClient(&redis.Options{
@ -10,5 +18,9 @@ func ConnectRedis(addr, password string, db int) error {
Password: password, Password: password,
DB: db, DB: db,
}) })
Kcc = &cachekit.Conn{
Rd: Rdb,
Timeout: 3 * time.Second,
}
return nil return nil
} }

View File

@ -12,6 +12,7 @@ import (
) )
func MapControllers(app *fiber.App) { func MapControllers(app *fiber.App) {
app.Get("/meet/:channel", renderMeetRoom)
app.Get("/captcha", renderCaptcha) app.Get("/captcha", renderCaptcha)
app.Post("/captcha", validateCaptcha) app.Post("/captcha", validateCaptcha)
app.Get("/check-ip", getClientIP) app.Get("/check-ip", getClientIP)

View File

@ -0,0 +1,55 @@
package api
import (
"fmt"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"github.com/gofiber/fiber/v2"
"github.com/spf13/viper"
"strings"
)
type meetRoomArgs struct {
RoomName string `json:"room_name"`
User meetRoomUser `json:"user"`
}
type meetRoomUser struct {
Avatar string `json:"avatar"`
Nick string `json:"nick"`
}
func renderMeetRoom(c *fiber.Ctx) error {
if err := sec.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("nex_user").(*sec.UserInfo)
channel := c.Params("channel")
var nick string
if val, ok := user.Metadata["nick"].(string); ok {
nick = val
} else {
nick = user.Name
}
var avatar string
if val, ok := user.Metadata["avatar"].(string); ok {
if strings.HasPrefix(val, "http") {
avatar = val
} else {
endpoint := viper.GetString("resources_endpoint")
avatar = fmt.Sprintf("%s/attachments/%s", endpoint, val)
}
avatar = fmt.Sprintf("\"%s\"", avatar) // Make the avatar a string to embed into the js
} else {
avatar = "undefined"
}
return c.Render("meet", meetRoomArgs{
RoomName: fmt.Sprintf("%s-%s", "sn-chat", channel),
User: meetRoomUser{
Avatar: avatar,
Nick: nick,
},
})
}

View File

@ -63,7 +63,7 @@ func NewServer() *WebApp {
Expiration: 60 * time.Second, Expiration: 60 * time.Second,
LimiterMiddleware: limiter.SlidingWindow{}, LimiterMiddleware: limiter.SlidingWindow{},
Next: func(c *fiber.Ctx) bool { Next: func(c *fiber.Ctx) bool {
return lo.Contains([]string{"GET", "HEAD", "OPTIONS", "CONNECT", "TRACE"}, c.Method()) return lo.Contains([]string{"POST", "PUT", "DELETE", "PATCH"}, c.Method())
}, },
})) }))
app.Use(limiter.New(limiter.Config{ app.Use(limiter.New(limiter.Config{
@ -71,7 +71,7 @@ func NewServer() *WebApp {
Expiration: 60 * time.Second, Expiration: 60 * time.Second,
LimiterMiddleware: limiter.SlidingWindow{}, LimiterMiddleware: limiter.SlidingWindow{},
Next: func(c *fiber.Ctx) bool { Next: func(c *fiber.Ctx) bool {
return lo.Contains([]string{"POST", "PUT", "DELETE", "PATCH"}, c.Method()) return lo.Contains([]string{"GET", "HEAD", "OPTIONS", "CONNECT", "TRACE"}, c.Method())
}, },
})) }))

View File

@ -0,0 +1,15 @@
package cachekit
import "fmt"
// Those constants are used to directly get the cached data from redis
// Formatted like {prefix}#{key}
const (
DAAttachment = "attachment"
DAUser = "account"
DARealm = "realm"
)
func FKey(prefix string, key any) string {
return fmt.Sprintf("%s#%v", prefix, key)
}

View File

@ -3,24 +3,41 @@ package cachekit
import ( import (
"context" "context"
"fmt" "fmt"
"sync"
"time" "time"
"git.solsynth.dev/hypernet/nexus/pkg/nex" "git.solsynth.dev/hypernet/nexus/pkg/nex"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
) )
// The global variable below is used to keep there will only be one redis client exist in a single instance
// Prevent if other DirectAccess™ SDK creating too many redis clients
// And able to recreate the conn with different options
var (
rdc *redis.Client
rdl sync.Mutex
)
type Conn struct { type Conn struct {
n *nex.Conn n *nex.Conn
Rd *redis.Client Rd *redis.Client
Timeout time.Duration Timeout time.Duration
} }
func NewCaConn(conn *nex.Conn, timeout time.Duration) (*Conn, error) { func NewConn(conn *nex.Conn, timeout time.Duration) (*Conn, error) {
rdl.Lock()
defer rdl.Unlock()
c := &Conn{ c := &Conn{
n: conn, n: conn,
Timeout: timeout, Timeout: timeout,
} }
if rdc != nil {
c.Rd = rdc
return c, nil
}
rdb := conn.AllocResource(nex.AllocatableResourceCache) rdb := conn.AllocResource(nex.AllocatableResourceCache)
if rdb == nil { if rdb == nil {
return nil, fmt.Errorf("unable to allocate resource: cache") return nil, fmt.Errorf("unable to allocate resource: cache")
@ -28,6 +45,7 @@ func NewCaConn(conn *nex.Conn, timeout time.Duration) (*Conn, error) {
return nil, fmt.Errorf("allocated cache resource is not a redis client") return nil, fmt.Errorf("allocated cache resource is not a redis client")
} else { } else {
c.Rd = client c.Rd = client
rdc = client
} }
return c, nil return c, nil

View File

@ -2,6 +2,8 @@ bind = "0.0.0.0:8001"
grpc_bind = "0.0.0.0:7001" grpc_bind = "0.0.0.0:7001"
domain = "localhost" domain = "localhost"
resources_endpoint = "https://api.sn.solsynth.dev/cgi/uc"
templates_dir = "./templates" templates_dir = "./templates"
ip_block_path = "./ip_block.list" ip_block_path = "./ip_block.list"

52
templates/meet.tmpl Normal file
View File

@ -0,0 +1,52 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Solar Network Meet</title>
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap"
rel="stylesheet"
/>
<style>
body {
width: 100vw;
height: 100vh;
padding: 0;
margin: 0;
}
.container {
width: 100vw;
height: 100vh;
}
</style>
<script src='https://meet.element.io/external_api.js'></script>
</head>
<body>
<div class="parent">
<div class="container" id="meet">
</div>
</div>
<script>
function getQueryParam(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
const node = document.querySelector('#meet');
const domain = 'meet.element.io';
const options = {
roomName: "{{ .RoomName }}",
width: '100%',
height: '100%',
parentNode: node,
userInfo: {
avatar: {{ .User.Avatar }},
displayName: "{{ .User.Nick }}"
}
};
const api = new JitsiMeetExternalAPI(domain, options)
</script>
</body>
</html>