Client UUID added to JWT
This commit is contained in:
parent
dfd6260a7a
commit
dc010df448
6 changed files with 65 additions and 38 deletions
|
@ -2,8 +2,9 @@ package authentication
|
|||
|
||||
import (
|
||||
// External
|
||||
_ "git.gibonuddevalla.se/go/wrappederror"
|
||||
werr "git.gibonuddevalla.se/go/wrappederror"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/lib/pq"
|
||||
|
||||
|
@ -146,6 +147,14 @@ func (mngr *Manager) AuthenticationHandler(w http.ResponseWriter, r *http.Reques
|
|||
data["uid"] = user.ID
|
||||
data["login"] = user.Username
|
||||
data["name"] = user.Name
|
||||
|
||||
data["cid"], err = mngr.NewClientUUID(user)
|
||||
if err != nil {
|
||||
mngr.log.Error("authentication", "error", err)
|
||||
httpError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
token, err = mngr.GenerateToken(data)
|
||||
if err != nil {
|
||||
mngr.log.Error("authentication", "error", err)
|
||||
|
@ -269,3 +278,31 @@ func (mngr *Manager) ChangePassword(username, currentPassword, newPassword strin
|
|||
changed = (rowsAffected == 1)
|
||||
return
|
||||
} // }}}
|
||||
func (mngr *Manager) NewClientUUID(user User) (clientUUID string, err error) { // {{{
|
||||
// Each client session has its own UUID.
|
||||
// Loop through until a unique one is established.
|
||||
var proposedClientUUID string
|
||||
var numSessions int
|
||||
for {
|
||||
proposedClientUUID = uuid.NewString()
|
||||
row := mngr.db.QueryRow("SELECT COUNT(id) FROM public.client WHERE client_uuid = $1", proposedClientUUID)
|
||||
err = row.Scan(&numSessions)
|
||||
if err != nil {
|
||||
err = werr.Wrap(err).WithData(proposedClientUUID)
|
||||
return
|
||||
}
|
||||
|
||||
if numSessions > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = mngr.db.Exec(`INSERT INTO public.client(user_id, client_uuid) VALUES($1, $2)`, user.ID, proposedClientUUID)
|
||||
if err != nil {
|
||||
err = werr.Wrap(err).WithData(proposedClientUUID)
|
||||
return
|
||||
}
|
||||
clientUUID = proposedClientUUID
|
||||
break
|
||||
}
|
||||
return
|
||||
} // }}}
|
||||
|
|
24
main.go
24
main.go
|
@ -167,7 +167,7 @@ func authenticated(fn func(http.ResponseWriter, *http.Request)) func(http.Respon
|
|||
user := NewUser(claims)
|
||||
r = r.WithContext(context.WithValue(r.Context(), CONTEXT_USER, user))
|
||||
|
||||
Log.Info("webserver", "op", "request", "method", r.Method, "url", r.URL.String(), "username", user.Username)
|
||||
Log.Debug("webserver", "op", "request", "method", r.Method, "url", r.URL.String(), "username", user.Username, "client", user.ClientUUID)
|
||||
fn(w, r)
|
||||
}
|
||||
} // }}}
|
||||
|
@ -246,22 +246,11 @@ func actionSyncFromServer(w http.ResponseWriter, r *http.Request) { // {{{
|
|||
// The purpose of the Client UUID is to avoid
|
||||
// sending nodes back once again to a client that
|
||||
// just created or modified it.
|
||||
request := struct {
|
||||
ClientUUID string
|
||||
}{}
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
err := json.Unmarshal(body, &request)
|
||||
if err != nil {
|
||||
Log.Error("/node/tree", "error", err)
|
||||
httpError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
user := getUser(r)
|
||||
changedFrom, _ := strconv.Atoi(r.PathValue("sequence"))
|
||||
offset, _ := strconv.Atoi(r.PathValue("offset"))
|
||||
|
||||
nodes, maxSeq, moreRowsExist, err := Nodes(user.ID, offset, uint64(changedFrom), request.ClientUUID)
|
||||
nodes, maxSeq, moreRowsExist, err := Nodes(user.UserID, offset, uint64(changedFrom), user.ClientUUID)
|
||||
if err != nil {
|
||||
Log.Error("/node/tree", "error", err)
|
||||
httpError(w, err)
|
||||
|
@ -285,7 +274,7 @@ func actionNodeRetrieve(w http.ResponseWriter, r *http.Request) { // {{{
|
|||
var err error
|
||||
|
||||
uuid := r.PathValue("uuid")
|
||||
node, err := RetrieveNode(user.ID, uuid)
|
||||
node, err := RetrieveNode(user.UserID, uuid)
|
||||
if err != nil {
|
||||
responseError(w, err)
|
||||
return
|
||||
|
@ -301,7 +290,6 @@ func actionSyncToServer(w http.ResponseWriter, r *http.Request) { // {{{
|
|||
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
var request struct {
|
||||
ClientUUID string
|
||||
NodeData string
|
||||
}
|
||||
err := json.Unmarshal(body, &request)
|
||||
|
@ -310,7 +298,7 @@ func actionSyncToServer(w http.ResponseWriter, r *http.Request) { // {{{
|
|||
return
|
||||
}
|
||||
|
||||
db.Exec(`CALL add_nodes($1, $2, $3::jsonb)`, user.ID, request.ClientUUID, request.NodeData)
|
||||
db.Exec(`CALL add_nodes($1, $2, $3::jsonb)`, user.UserID, user.ClientUUID, request.NodeData)
|
||||
|
||||
responseData(w, map[string]interface{}{
|
||||
"OK": true,
|
||||
|
@ -359,7 +347,7 @@ func changePassword(username string) { // {{{
|
|||
|
||||
fmt.Printf("\nPassword changed\n")
|
||||
} // }}}
|
||||
func getUser(r *http.Request) User { // {{{
|
||||
user, _ := r.Context().Value(CONTEXT_USER).(User)
|
||||
func getUser(r *http.Request) UserSession { // {{{
|
||||
user, _ := r.Context().Value(CONTEXT_USER).(UserSession)
|
||||
return user
|
||||
} // }}}
|
||||
|
|
10
sql/00010.sql
Normal file
10
sql/00010.sql
Normal file
|
@ -0,0 +1,10 @@
|
|||
CREATE TABLE public.client (
|
||||
id serial NOT NULL,
|
||||
user_id int4 NOT NULL,
|
||||
client_uuid bpchar(36) DEFAULT '' NOT NULL,
|
||||
created timestamptz DEFAULT NOW() NOT NULL,
|
||||
description varchar DEFAULT '' NOT NULL,
|
||||
CONSTRAINT client_pk PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX client_uuid_idx ON public.client (client_uuid);
|
|
@ -70,7 +70,6 @@ export class NodeStore {
|
|||
this.sendQueue = new SimpleNodeStore(this.db, 'send_queue')
|
||||
this.nodesHistory = new SimpleNodeStore(this.db, 'nodes_history')
|
||||
this.initializeRootNode()
|
||||
.then(() => this.initializeClientUUID())
|
||||
.then(() => resolve())
|
||||
}
|
||||
|
||||
|
@ -110,13 +109,6 @@ export class NodeStore {
|
|||
getRequest.onerror = (event) => reject(event.target.error)
|
||||
})
|
||||
}//}}}
|
||||
async initializeClientUUID() {//{{{
|
||||
let clientUUID = await this.getAppState('client_uuid')
|
||||
if (clientUUID !== null)
|
||||
return
|
||||
clientUUID = crypto.randomUUID()
|
||||
return this.setAppState('client_uuid', clientUUID)
|
||||
}//}}}
|
||||
|
||||
node(uuid, dataIfUndefined, newLevel) {//{{{
|
||||
let n = this.nodes[uuid]
|
||||
|
|
|
@ -13,7 +13,6 @@ export class Sync {
|
|||
// The latest sync node value is used to retrieve the changes
|
||||
// from the backend.
|
||||
const state = await nodeStore.getAppState('latest_sync_node')
|
||||
const clientUUID = await nodeStore.getAppState('client_uuid')
|
||||
const oldMax = (state?.value ? state.value : 0)
|
||||
let currMax = oldMax
|
||||
|
||||
|
@ -22,7 +21,7 @@ export class Sync {
|
|||
let batch = 0
|
||||
do {
|
||||
batch++
|
||||
res = await API.query('POST', `/sync/from_server/${oldMax}/${offset}`, { ClientUUID: clientUUID.value })
|
||||
res = await API.query('POST', `/sync/from_server/${oldMax}/${offset}`)
|
||||
if (res.Nodes.length > 0)
|
||||
console.log(`Node sync batch #${batch}`)
|
||||
offset += res.Nodes.length
|
||||
|
@ -96,10 +95,8 @@ export class Sync {
|
|||
break
|
||||
console.debug(`Sending ${nodesToSend.length} node(s) to server`)
|
||||
|
||||
const clientUUID = await nodeStore.getAppState('client_uuid')
|
||||
const request = {
|
||||
NodeData: JSON.stringify(nodesToSend),
|
||||
ClientUUID: clientUUID.value,
|
||||
}
|
||||
const res = await API.query('POST', '/sync/to_server', request)
|
||||
if (!res.OK) {
|
||||
|
|
11
user.go
11
user.go
|
@ -5,20 +5,23 @@ import (
|
|||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int
|
||||
type UserSession struct {
|
||||
UserID int
|
||||
Username string
|
||||
Password string
|
||||
Name string
|
||||
ClientUUID string
|
||||
}
|
||||
|
||||
func NewUser(claims jwt.MapClaims) (u User) {
|
||||
func NewUser(claims jwt.MapClaims) (u UserSession) {
|
||||
uid, _ := claims["uid"].(float64)
|
||||
name, _ := claims["name"].(string)
|
||||
username, _ := claims["login"].(string)
|
||||
clientUUID, _ := claims["cid"].(string)
|
||||
|
||||
u.ID = int(uid)
|
||||
u.UserID = int(uid)
|
||||
u.Username = username
|
||||
u.Name = name
|
||||
u.ClientUUID = clientUUID
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue