add client and groups member logic
This commit is contained in:
@@ -1,33 +0,0 @@
|
|||||||
# TODO — Code Logic Errors
|
|
||||||
|
|
||||||
## Critical
|
|
||||||
|
|
||||||
- [ ] **Login: nil pointer dereference** (`http.go:111`)
|
|
||||||
`CacheGetClientByName` returns `nil` on miss, then `DbSetClientByName` is called with that nil `client` → panic. Should query DB by username directly.
|
|
||||||
|
|
||||||
- [ ] **Login: password never verified** (`http.go:87–131`)
|
|
||||||
No call to `PasswordVerify`/`bcrypt.CompareHashAndPassword`. Anyone with a valid username can log in.
|
|
||||||
|
|
||||||
## High
|
|
||||||
|
|
||||||
- [ ] **Login: validates `username` length instead of `password`** (`http.go:98`)
|
|
||||||
`if len(username) < 8` should be `if len(password) < 8`. Password is never length-checked.
|
|
||||||
|
|
||||||
- [ ] **DB: missing `&` in `Scan` for `pronouns`** (`database.go:87`)
|
|
||||||
`client.Pronouns` should be `&client.Pronouns`. Compare with `DbSetClientById` which does it correctly.
|
|
||||||
|
|
||||||
- [ ] **WS: 30s context kills entire connection** (`wsServer.go:23`)
|
|
||||||
A single 30s timeout context is shared across all reads in the loop. Should use per-read deadlines or `context.Background()` for the loop.
|
|
||||||
|
|
||||||
## Medium
|
|
||||||
|
|
||||||
- [ ] **NewUser: missing `return` after bad color error** (`http.go:54–56`)
|
|
||||||
On `parseRgb` error, `http.Error` is called but execution continues with `color = [0,0,0]`.
|
|
||||||
|
|
||||||
- [ ] **WS: unauth disconnect deletes ID=0 from cache** (`wsServer.go:115`)
|
|
||||||
`closeConnection` calls `CacheDeleteClient(client.Id)` but unauthenticated clients have `Id=0`, wiping whatever sits at key 0.
|
|
||||||
|
|
||||||
## Low
|
|
||||||
|
|
||||||
- [ ] **`CacheSetGroup` is a no-op** (`cache.go:59`)
|
|
||||||
Function body is empty. The `Groups` cache is never populated, so every `CacheGetGroup` call misses and falls back to DB.
|
|
||||||
@@ -42,7 +42,7 @@ func CacheGetClientByName(name string) (*Client, error) {
|
|||||||
return nil, fmt.Errorf("client %s not found", name)
|
return nil, fmt.Errorf("client %s not found", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CacheSetClient(client *Client) {
|
func CacheSaveClient(client *Client) {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
@@ -56,7 +56,12 @@ func CacheDeleteClient(id uint32) {
|
|||||||
delete(CacheClients, id)
|
delete(CacheClients, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CacheSetGroup() {}
|
func CacheSaveGroup(group *Group) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
Groups[group.Id] = group
|
||||||
|
}
|
||||||
|
|
||||||
func CacheGetGroup(id uint32) (*Group, error) {
|
func CacheGetGroup(id uint32) (*Group, error) {
|
||||||
mu.RLock()
|
mu.RLock()
|
||||||
|
|||||||
+66
-16
@@ -18,7 +18,7 @@ func DbInit(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err = dbConn.Exec(ctx, `
|
_, err = dbConn.Exec(ctx, `
|
||||||
CREATE TABLE IF NOT EXISTS client (
|
CREATE TABLE IF NOT EXISTS clients (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
name VARCHAR(20) UNIQUE NOT NULL,
|
name VARCHAR(20) UNIQUE NOT NULL,
|
||||||
pass_hash VARCHAR(60) NOT NULL,
|
pass_hash VARCHAR(60) NOT NULL,
|
||||||
@@ -37,8 +37,8 @@ func DbInit(ctx context.Context) {
|
|||||||
CREATE TABLE IF NOT EXISTS chat_groups (
|
CREATE TABLE IF NOT EXISTS chat_groups (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
name VARCHAR(48) NOT NULL,
|
name VARCHAR(48) NOT NULL,
|
||||||
creator_id INTEGER NOT NULL REFERENCES client(id) ON DELETE CASCADE,
|
creator_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||||
owner_id INTEGER NOT NULL REFERENCES client(id) ON DELETE CASCADE,
|
owner_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||||
enable_client_colors BOOLEAN NOT NULL DEFAULT true,
|
enable_client_colors BOOLEAN NOT NULL DEFAULT true,
|
||||||
color_red SMALLINT DEFAULT NULL,
|
color_red SMALLINT DEFAULT NULL,
|
||||||
color_green SMALLINT DEFAULT NULL,
|
color_green SMALLINT DEFAULT NULL,
|
||||||
@@ -53,7 +53,7 @@ func DbInit(ctx context.Context) {
|
|||||||
_, err = dbConn.Exec(ctx, `
|
_, err = dbConn.Exec(ctx, `
|
||||||
CREATE TABLE IF NOT EXISTS chat_group_members (
|
CREATE TABLE IF NOT EXISTS chat_group_members (
|
||||||
group_id INTEGER NOT NULL REFERENCES chat_groups(id) ON DELETE CASCADE,
|
group_id INTEGER NOT NULL REFERENCES chat_groups(id) ON DELETE CASCADE,
|
||||||
user_id INTEGER NOT NULL REFERENCES client(id) ON DELETE CASCADE,
|
user_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||||
joined_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
joined_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
PRIMARY KEY (group_id, user_id)
|
PRIMARY KEY (group_id, user_id)
|
||||||
)
|
)
|
||||||
@@ -73,22 +73,17 @@ func DbSaveClientWithoutGroups(ctx context.Context, client *Client) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func DbGetIdByClientName(ctx context.Context, name string) (uint32, error) {
|
|
||||||
var id uint32
|
|
||||||
err := dbConn.QueryRow(ctx, `
|
|
||||||
SELECT id FROM clients WHERE name = $1
|
|
||||||
`, name).Scan(&id)
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func DbSetClientByName(ctx context.Context, client *Client) error {
|
func DbSetClientByName(ctx context.Context, client *Client) error {
|
||||||
err := dbConn.QueryRow(ctx, `
|
err := dbConn.QueryRow(ctx, `
|
||||||
SELECT name, pass_hash, color_red, color_green, color_blue, created_at FROM clients WHERE name = $1
|
SELECT id, name, pass_hash, pronouns, color_red, color_green, color_blue, created_at FROM clients WHERE name = $1
|
||||||
`, client.Name).Scan(&client.Name, &client.PasswordHash, &client.Pronouns, &client.Color[0], &client.Color[1], &client.Color[2], &client.CreatedAt)
|
`, client.Name).Scan(&client.Id, &client.Name, &client.PasswordHash, &client.Pronouns, &client.Color[0], &client.Color[1], &client.Color[2], &client.CreatedAt)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
return DbSetClientGroups(ctx, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DbSetClientById(ctx context.Context, client *Client) error {
|
func DbSetClientByIdWithoutGroups(ctx context.Context, client *Client) error {
|
||||||
err := dbConn.QueryRow(ctx, `
|
err := dbConn.QueryRow(ctx, `
|
||||||
SELECT name, pass_hash, pronouns, color_red, color_green, color_blue, created_at FROM clients WHERE id = $1
|
SELECT name, pass_hash, pronouns, color_red, color_green, color_blue, created_at FROM clients WHERE id = $1
|
||||||
`, client.Id).Scan(&client.Name, &client.PasswordHash, &client.Pronouns, &client.Color[0], &client.Color[1], &client.Color[2], &client.CreatedAt)
|
`, client.Id).Scan(&client.Name, &client.PasswordHash, &client.Pronouns, &client.Color[0], &client.Color[1], &client.Color[2], &client.CreatedAt)
|
||||||
@@ -102,10 +97,17 @@ func DbSaveGroupWithoutClients(ctx context.Context, group *Group) error {
|
|||||||
RETURNING id
|
RETURNING id
|
||||||
`, group.Name, group.CreatorId, group.OwnerId, group.EnableClientColors, group.Color[0], group.Color[1], group.Color[2], group.CreatedAt).
|
`, group.Name, group.CreatorId, group.OwnerId, group.EnableClientColors, group.Color[0], group.Color[1], group.Color[2], group.CreatedAt).
|
||||||
Scan(&group.Id)
|
Scan(&group.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = dbConn.Exec(ctx, `
|
||||||
|
INSERT INTO chat_group_members (group_id, user_id, joined_at)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
`, group.Id, group.OwnerId, group.CreatedAt)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func DbSetGroupById(ctx context.Context, group *Group) error {
|
func DbSetGroupByIdWithoutClients(ctx context.Context, group *Group) error {
|
||||||
err := dbConn.QueryRow(ctx, `
|
err := dbConn.QueryRow(ctx, `
|
||||||
SELECT name, creator_id, owner_id, enable_client_colors, color_red, color_green, color_blue, created_at FROM chat_groups WHERE id = $1
|
SELECT name, creator_id, owner_id, enable_client_colors, color_red, color_green, color_blue, created_at FROM chat_groups WHERE id = $1
|
||||||
`, group.Id).Scan(&group.Name, &group.CreatorId, &group.OwnerId, &group.EnableClientColors, &group.Color[0], &group.Color[1], &group.Color[2], &group.CreatedAt)
|
`, group.Id).Scan(&group.Name, &group.CreatorId, &group.OwnerId, &group.EnableClientColors, &group.Color[0], &group.Color[1], &group.Color[2], &group.CreatedAt)
|
||||||
@@ -132,6 +134,34 @@ func DbSetGroupById(ctx context.Context, group *Group) error {
|
|||||||
return rows.Err()
|
return rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DbSetGroupById(ctx context.Context, group *Group) error {
|
||||||
|
err := DbSetGroupByIdWithoutClients(ctx, group)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return DbSetGroupMemberClients(ctx, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DbSetGroupMemberClients(ctx context.Context, group *Group) error {
|
||||||
|
rows, err := dbConn.Query(ctx, `
|
||||||
|
SELECT user_id FROM chat_group_members WHERE group_id = $1
|
||||||
|
`, group.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
group.Clients = make(map[uint32]struct{})
|
||||||
|
for rows.Next() {
|
||||||
|
var userId uint32
|
||||||
|
if err := rows.Scan(&userId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
group.Clients[userId] = struct{}{}
|
||||||
|
}
|
||||||
|
return rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
func DbAddClientsToGroup(ctx context.Context, groupId uint32, clientIds []uint32) error {
|
func DbAddClientsToGroup(ctx context.Context, groupId uint32, clientIds []uint32) error {
|
||||||
batch := &pgx.Batch{}
|
batch := &pgx.Batch{}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
@@ -151,3 +181,23 @@ func DbAddClientsToGroup(ctx context.Context, groupId uint32, clientIds []uint32
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DbSetClientGroups(ctx context.Context, client *Client) error {
|
||||||
|
rows, err := dbConn.Query(ctx, `
|
||||||
|
SELECT group_id FROM chat_group_members WHERE user_id = $1
|
||||||
|
`, client.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
client.Groups = make(map[uint32]struct{})
|
||||||
|
for rows.Next() {
|
||||||
|
var groupId uint32
|
||||||
|
if err := rows.Scan(&groupId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client.Groups[groupId] = struct{}{}
|
||||||
|
}
|
||||||
|
return rows.Err()
|
||||||
|
}
|
||||||
|
|||||||
@@ -117,15 +117,15 @@ func HttpHandleLogin(response http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
err := DbSetClientByName(ctx, client)
|
err := DbSetClientByName(ctx, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "bad login", http.StatusUnauthorized)
|
http.Error(response, "bad login1", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
CacheSetClient(client)
|
CacheSaveClient(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = bcrypt.CompareHashAndPassword([]byte(client.PasswordHash), []byte(password))
|
err = bcrypt.CompareHashAndPassword([]byte(client.PasswordHash), []byte(password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "bad login", http.StatusUnauthorized)
|
http.Error(response, "bad login2", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ func HttpHandleGroupCreate(response http.ResponseWriter, request *http.Request)
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
client = *cacheClient
|
client = *cacheClient
|
||||||
} else {
|
} else {
|
||||||
err = DbSetClientById(ctx, &client)
|
err = DbSetClientByIdWithoutGroups(ctx, &client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
http.Error(response, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -191,6 +191,7 @@ func HttpHandleGroupCreate(response http.ResponseWriter, request *http.Request)
|
|||||||
OwnerId: clientId,
|
OwnerId: clientId,
|
||||||
CreatorId: clientId,
|
CreatorId: clientId,
|
||||||
Color: color,
|
Color: color,
|
||||||
|
Clients: map[uint32]struct{}{clientId: {}},
|
||||||
}
|
}
|
||||||
|
|
||||||
enableClientColors := request.FormValue("enableClientColors")
|
enableClientColors := request.FormValue("enableClientColors")
|
||||||
@@ -200,14 +201,14 @@ func HttpHandleGroupCreate(response http.ResponseWriter, request *http.Request)
|
|||||||
|
|
||||||
err = DbSaveGroupWithoutClients(ctx, &group)
|
err = DbSaveGroupWithoutClients(ctx, &group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "internal server error", http.StatusInternalServerError)
|
http.Error(response, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
groupIdBytes := make([]byte, 4)
|
groupIdBytes := make([]byte, 4)
|
||||||
binary.BigEndian.PutUint32(groupIdBytes, group.Id)
|
binary.BigEndian.PutUint32(groupIdBytes, group.Id)
|
||||||
|
|
||||||
response.WriteHeader(http.StatusCreated)
|
response.WriteHeader(http.StatusCreated)
|
||||||
response.Write([]byte(groupIdBytes))
|
response.Write(groupIdBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpHandleGroupAddClient(response http.ResponseWriter, request *http.Request) {
|
func HttpHandleGroupAddClient(response http.ResponseWriter, request *http.Request) {
|
||||||
@@ -236,7 +237,7 @@ func HttpHandleGroupAddClient(response http.ResponseWriter, request *http.Reques
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
group = *groupPtr
|
group = *groupPtr
|
||||||
} else {
|
} else {
|
||||||
err = DbSetGroupById(ctx, &group)
|
err = DbSetGroupByIdWithoutClients(ctx, &group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(response, "no such group", http.StatusUnauthorized)
|
http.Error(response, "no such group", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -72,13 +72,14 @@
|
|||||||
<button class="tab-btn active" onclick="switchTab('login')">Login</button>
|
<button class="tab-btn active" onclick="switchTab('login')">Login</button>
|
||||||
<button class="tab-btn" onclick="switchTab('register')">Register</button>
|
<button class="tab-btn" onclick="switchTab('register')">Register</button>
|
||||||
<button class="tab-btn" onclick="switchTab('connect')">Connect</button>
|
<button class="tab-btn" onclick="switchTab('connect')">Connect</button>
|
||||||
|
<button class="tab-btn" onclick="switchTab('group')">Group</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tab-login" class="tab active">
|
<div id="tab-login" class="tab active">
|
||||||
<label>Username</label>
|
<label>Username</label>
|
||||||
<input id="login-username" type="text" placeholder="alice">
|
<input id="login-username" type="text" placeholder="alice">
|
||||||
<label style="margin-top:10px;">Password</label>
|
<label style="margin-top:10px;">Password</label>
|
||||||
<input id="login-password" type="password" placeholder="••••••••">
|
<input id="login-password" type="text" placeholder="password">
|
||||||
<button onclick="login()">Login</button>
|
<button onclick="login()">Login</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -86,7 +87,7 @@
|
|||||||
<label>Username</label>
|
<label>Username</label>
|
||||||
<input id="reg-username" type="text" placeholder="alice">
|
<input id="reg-username" type="text" placeholder="alice">
|
||||||
<label style="margin-top:10px;">Password</label>
|
<label style="margin-top:10px;">Password</label>
|
||||||
<input id="reg-password" type="password" placeholder="••••••••">
|
<input id="reg-password" type="text" placeholder="password">
|
||||||
<label style="margin-top:10px;">Color (r,g,b)</label>
|
<label style="margin-top:10px;">Color (r,g,b)</label>
|
||||||
<input id="reg-color" type="text" placeholder="255,100,50">
|
<input id="reg-color" type="text" placeholder="255,100,50">
|
||||||
<button onclick="register()">Register</button>
|
<button onclick="register()">Register</button>
|
||||||
@@ -100,6 +101,18 @@
|
|||||||
<button id="connect-btn" onclick="connect()">Connect</button>
|
<button id="connect-btn" onclick="connect()">Connect</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="tab-group" class="tab">
|
||||||
|
<label>Token</label>
|
||||||
|
<input id="group-token" type="text" placeholder="Paste token from login">
|
||||||
|
<label style="margin-top:10px;">Name (optional)</label>
|
||||||
|
<input id="group-name" type="text" placeholder="Best group ever">
|
||||||
|
<label style="margin-top:10px;">Color (r,g,b or red/green/blue)</label>
|
||||||
|
<input id="group-color" type="text" placeholder="255,100,50">
|
||||||
|
<label style="margin-top:10px;">Enable client colors</label>
|
||||||
|
<input id="group-client-colors" type="text" placeholder="1 or 0">
|
||||||
|
<button onclick="createGroup()">Create Group</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="form-status"></div>
|
<div id="form-status"></div>
|
||||||
<div id="status">Not connected</div>
|
<div id="status">Not connected</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,7 +154,7 @@
|
|||||||
|
|
||||||
const body = new URLSearchParams({ username, password, color });
|
const body = new URLSearchParams({ username, password, color });
|
||||||
try {
|
try {
|
||||||
const res = await fetch('http://localhost:8080/newuser', { method: 'POST', body });
|
const res = await fetch('http://localhost:8080/new/client', { method: 'POST', body });
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
setFormStatus('Registered! Now login.', 'ok');
|
setFormStatus('Registered! Now login.', 'ok');
|
||||||
@@ -160,11 +173,12 @@
|
|||||||
|
|
||||||
const body = new URLSearchParams({ username, password });
|
const body = new URLSearchParams({ username, password });
|
||||||
try {
|
try {
|
||||||
const res = await fetch('http://localhost:8080/login', { method: 'POST', body });
|
const res = await fetch('http://localhost:8080/new/token', { method: 'POST', body });
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
document.getElementById('username-input').value = username;
|
document.getElementById('username-input').value = username;
|
||||||
document.getElementById('token-input').value = text;
|
document.getElementById('token-input').value = text;
|
||||||
|
document.getElementById('group-token').value = text;
|
||||||
setFormStatus('Logged in! Token copied to Connect tab.', 'ok');
|
setFormStatus('Logged in! Token copied to Connect tab.', 'ok');
|
||||||
switchTab('connect');
|
switchTab('connect');
|
||||||
} else {
|
} else {
|
||||||
@@ -175,6 +189,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createGroup() {
|
||||||
|
const token = document.getElementById('group-token').value.trim();
|
||||||
|
if (!token) { setFormStatus('Paste a token first', 'err'); return; }
|
||||||
|
|
||||||
|
const body = new URLSearchParams({ token });
|
||||||
|
const name = document.getElementById('group-name').value.trim();
|
||||||
|
if (name) body.set('name', name);
|
||||||
|
const color = document.getElementById('group-color').value.trim();
|
||||||
|
if (color) body.set('color', color);
|
||||||
|
const enableClientColors = document.getElementById('group-client-colors').value.trim();
|
||||||
|
if (enableClientColors) body.set('enableClientColors', enableClientColors);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('http://localhost:8080/new/group', { method: 'POST', body });
|
||||||
|
if (res.ok) {
|
||||||
|
const buf = await res.arrayBuffer();
|
||||||
|
const id = new DataView(buf).getUint32(0, false);
|
||||||
|
setFormStatus('Group created! ID: ' + id, 'ok');
|
||||||
|
} else {
|
||||||
|
setFormStatus('Error: ' + await res.text(), 'err');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setFormStatus('Request failed: ' + e.message, 'err');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setStatus(text, cls) {
|
function setStatus(text, cls) {
|
||||||
const el = document.getElementById('status');
|
const el = document.getElementById('status');
|
||||||
el.textContent = text;
|
el.textContent = text;
|
||||||
|
|||||||
@@ -6,14 +6,21 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func withCORS(h http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
h(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
DbInit(ctx)
|
DbInit(ctx)
|
||||||
|
|
||||||
http.HandleFunc("/newuser", HttpHandleNewUser)
|
http.HandleFunc("/new/client", withCORS(HttpHandleNewUser))
|
||||||
http.HandleFunc("/login", HttpHandleLogin)
|
http.HandleFunc("/new/token", withCORS(HttpHandleLogin))
|
||||||
http.HandleFunc("/group/create", HttpHandleGroupCreate)
|
http.HandleFunc("/new/group", withCORS(HttpHandleGroupCreate))
|
||||||
http.HandleFunc("/group/addclient", HttpHandleGroupAddClient)
|
http.HandleFunc("/mod/group/addclients", withCORS(HttpHandleGroupAddClient))
|
||||||
http.HandleFunc("/ws", ServeWsConnection)
|
http.HandleFunc("/ws", ServeWsConnection)
|
||||||
|
|
||||||
log.Println("listening on :8080")
|
log.Println("listening on :8080")
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
crash on adding to group
|
||||||
|
|
||||||
|
2026/03/27 18:44:04 listening on :8080
|
||||||
|
2026/03/27 18:45:17 read error: failed to read JSON message: failed to get reader: received close frame: status = StatusNoStatusRcvd and reason = ""
|
||||||
|
2026/03/27 18:45:17 http: panic serving 127.0.0.1:54644: runtime error: invalid memory address or nil pointer dereference
|
||||||
|
goroutine 25 [running]:
|
||||||
|
net/http.(*conn).serve.func1()
|
||||||
|
/usr/lib/go/src/net/http/server.go:1907 +0xbd
|
||||||
|
panic({0x8e0f20?, 0xe2b190?})
|
||||||
|
/usr/lib/go/src/runtime/panic.go:860 +0x13a
|
||||||
|
github.com/coder/websocket.(*Conn).casClosing(...)
|
||||||
|
/home/ffus/go/pkg/mod/github.com/coder/websocket@v1.8.14/close.go:325
|
||||||
|
github.com/coder/websocket.(*Conn).CloseNow(0x2d8e00000002?)
|
||||||
|
/home/ffus/go/pkg/mod/github.com/coder/websocket@v1.8.14/close.go:135 +0x48
|
||||||
|
main.closeConnection(0x2d8ef7ddd990?, 0xb8?)
|
||||||
|
/home/ffus/Projects/go-socket/wsServer.go:183 +0x2d
|
||||||
|
main.ServeWsConnection({0x9c4a68?, 0x2d8ef7ee41e0?}, 0x2d8ef7d5db30?)
|
||||||
|
/home/ffus/Projects/go-socket/wsServer.go:36 +0x350
|
||||||
|
net/http.HandlerFunc.ServeHTTP(0xe43400?, {0x9c4a68?, 0x2d8ef7ee41e0?}, 0x7e1176?)
|
||||||
|
/usr/lib/go/src/net/http/server.go:2286 +0x29
|
||||||
|
net/http.(*ServeMux).ServeHTTP(0x482d39?, {0x9c4a68, 0x2d8ef7ee41e0}, 0x2d8ef7de4a00)
|
||||||
|
/usr/lib/go/src/net/http/server.go:2828 +0x1c7
|
||||||
|
net/http.serverHandler.ServeHTTP({0x2d8ef7d748c0?}, {0x9c4a68?, 0x2d8ef7ee41e0?}, 0x6?)
|
||||||
|
/usr/lib/go/src/net/http/server.go:3311 +0x8e
|
||||||
|
net/http.(*conn).serve(0x2d8ef7df8360, {0x9c5e48, 0x2d8ef7e1c570})
|
||||||
|
/usr/lib/go/src/net/http/server.go:2073 +0x650
|
||||||
|
created by net/http.(*Server).Serve in goroutine 1
|
||||||
|
/usr/lib/go/src/net/http/server.go:3464 +0x485
|
||||||
|
2026/03/27 18:47:47 read error: failed to read JSON message: failed to get reader: received close frame: status = StatusNoStatusRcvd and reason = ""
|
||||||
|
2026/03/27 18:47:47 http: panic serving 127.0.0.1:54648: runtime error: invalid memory address or nil pointer dereference
|
||||||
|
goroutine 53 [running]:
|
||||||
|
net/http.(*conn).serve.func1()
|
||||||
|
/usr/lib/go/src/net/http/server.go:1907 +0xbd
|
||||||
|
panic({0x8e0f20?, 0xe2b190?})
|
||||||
|
/usr/lib/go/src/runtime/panic.go:860 +0x13a
|
||||||
|
github.com/coder/websocket.(*Conn).casClosing(...)
|
||||||
|
/home/ffus/go/pkg/mod/github.com/coder/websocket@v1.8.14/close.go:325
|
||||||
|
github.com/coder/websocket.(*Conn).CloseNow(0x2d8e00000001?)
|
||||||
|
/home/ffus/go/pkg/mod/github.com/coder/websocket@v1.8.14/close.go:135 +0x48
|
||||||
|
main.closeConnection(0x2d8ef7f61990?, 0xb8?)
|
||||||
|
/home/ffus/Projects/go-socket/wsServer.go:183 +0x2d
|
||||||
|
main.ServeWsConnection({0x9c4a68?, 0x2d8ef7f2c1e0?}, 0x2d8ef7f56b30?)
|
||||||
|
/home/ffus/Projects/go-socket/wsServer.go:36 +0x350
|
||||||
|
net/http.HandlerFunc.ServeHTTP(0xe43400?, {0x9c4a68?, 0x2d8ef7f2c1e0?}, 0x7e1176?)
|
||||||
|
/usr/lib/go/src/net/http/server.go:2286 +0x29
|
||||||
|
net/http.(*ServeMux).ServeHTTP(0x482d39?, {0x9c4a68, 0x2d8ef7f2c1e0}, 0x2d8ef7f26140)
|
||||||
|
/usr/lib/go/src/net/http/server.go:2828 +0x1c7
|
||||||
|
net/http.serverHandler.ServeHTTP({0x2d8ef7f38080?}, {0x9c4a68?, 0x2d8ef7f2c1e0?}, 0x6?)
|
||||||
|
/usr/lib/go/src/net/http/server.go:3311 +0x8e
|
||||||
|
net/http.(*conn).serve(0x2d8ef7f2a2d0, {0x9c5e48, 0x2d8ef7e1c570})
|
||||||
|
/usr/lib/go/src/net/http/server.go:2073 +0x650
|
||||||
|
created by net/http.(*Server).Serve in goroutine 1
|
||||||
|
/usr/lib/go/src/net/http/server.go:3464 +0x485
|
||||||
+66
-3
@@ -42,7 +42,7 @@ func ServeWsConnection(responseWriter http.ResponseWriter, request *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !handleUnauthenticatedMessage(&client, &clientMessage) {
|
if !handleUnauthenticatedMessage(ctx, &client, &clientMessage) {
|
||||||
ignoreCache = true
|
ignoreCache = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -53,6 +53,10 @@ func ServeWsConnection(responseWriter http.ResponseWriter, request *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendMessageCloseIfTimeout(client *Client, message *map[string]any) {
|
func sendMessageCloseIfTimeout(client *Client, message *map[string]any) {
|
||||||
|
if client.WsConn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -74,11 +78,52 @@ func sendToAllMessageCloseIfTimeout(message *map[string]any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleAuthenticatedMessage(client *Client, clientMessage *map[string]any) bool {
|
func handleAuthenticatedMessage(client *Client, clientMessage *map[string]any) bool {
|
||||||
sendToAllMessageCloseIfTimeout(clientMessage)
|
subject, ok := (*clientMessage)["subject"].(uint32)
|
||||||
|
if !ok {
|
||||||
|
var msg = map[string]any{
|
||||||
|
"from": "server",
|
||||||
|
"error": "subject invalid",
|
||||||
|
}
|
||||||
|
sendMessageCloseIfTimeout(client, &msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
content, ok := (*clientMessage)["content"].(string)
|
||||||
|
if !ok {
|
||||||
|
var msg = map[string]any{
|
||||||
|
"from": "server",
|
||||||
|
"error": "content invalid",
|
||||||
|
}
|
||||||
|
sendMessageCloseIfTimeout(client, &msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := CacheGetGroup(subject)
|
||||||
|
if err != nil {
|
||||||
|
var msg = map[string]any{
|
||||||
|
"from": "server",
|
||||||
|
"error": "subject invalid",
|
||||||
|
}
|
||||||
|
sendMessageCloseIfTimeout(client, &msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
for groupClientId, _ := range group.Clients {
|
||||||
|
var msg = map[string]any{
|
||||||
|
"from": "group",
|
||||||
|
"group": group.Id,
|
||||||
|
"sender": client.Name,
|
||||||
|
"content": content,
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupClient *Client
|
||||||
|
groupClient, err = CacheGetClientById(groupClientId)
|
||||||
|
if err != nil {
|
||||||
|
sendMessageCloseIfTimeout(groupClient, &msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUnauthenticatedMessage(client *Client, clientMessage *map[string]any) bool {
|
func handleUnauthenticatedMessage(ctx context.Context, client *Client, clientMessage *map[string]any) bool {
|
||||||
token, ok := (*clientMessage)["token"].(string)
|
token, ok := (*clientMessage)["token"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
var msg = map[string]any{
|
var msg = map[string]any{
|
||||||
@@ -110,6 +155,24 @@ func handleUnauthenticatedMessage(client *Client, clientMessage *map[string]any)
|
|||||||
}
|
}
|
||||||
|
|
||||||
*client = *clientFromCache
|
*client = *clientFromCache
|
||||||
|
|
||||||
|
for groupId, _ := range clientFromCache.Groups {
|
||||||
|
_, err = CacheGetGroup(groupId)
|
||||||
|
if err != nil {
|
||||||
|
dbGroup := &Group{Id: groupId}
|
||||||
|
|
||||||
|
err = DbSetGroupById(ctx, dbGroup)
|
||||||
|
if err != nil {
|
||||||
|
var msg = map[string]any{
|
||||||
|
"from": "server",
|
||||||
|
"error": "invalid client data",
|
||||||
|
}
|
||||||
|
sendMessageCloseIfTimeout(client, &msg)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
CacheSaveGroup(dbGroup)
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user