ci / verify (push) Canceled after 0s
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
69 lines
3.4 KiB
Go
69 lines
3.4 KiB
Go
package callmemory
|
|
|
|
import (
|
|
"context"
|
|
"data-terminal-cloud/internal/identity"
|
|
"encoding/json"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
type AccessInput struct {
|
|
Elder string `json:"elder_id"`
|
|
User string `json:"user_id,omitempty"`
|
|
Read bool `json:"can_read"`
|
|
Write bool `json:"can_write"`
|
|
Sensitive bool `json:"sensitive_read"`
|
|
Version int64 `json:"expected_version"`
|
|
}
|
|
|
|
func (s *Service) accessRequest(ctx context.Context, tx pgx.Tx, p identity.Principal, op string, raw []byte) (any, error) {
|
|
var in AccessInput
|
|
if e := StrictJSON(raw, &in); e != nil {
|
|
return nil, e
|
|
}
|
|
a, e := ResolveAccess(ctx, tx, p, in.Elder, false)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
if !a.Manage {
|
|
return nil, fault(403, "MEMORY_ACCESS_MANAGE_DENIED")
|
|
}
|
|
if op == "access/grant" {
|
|
if !validUUID(in.User) || in.Version < 0 || (!in.Read && (in.Write || in.Sensitive)) {
|
|
return nil, fault(400, "INVALID_MEMORY_GRANT")
|
|
}
|
|
var user, role string
|
|
e = tx.QueryRow(ctx, `SELECT ua.id::text,hm.role FROM user_account ua JOIN household_member hm ON hm.user_id=ua.id JOIN subject_profile sp ON sp.household_id=hm.household_id WHERE sp.id=$1::uuid AND ua.public_id=$2::uuid AND hm.status='active' AND ua.status='active' FOR UPDATE OF hm`, a.Subject, in.User).Scan(&user, &role)
|
|
if e != nil {
|
|
return nil, fault(403, "MEMORY_ACCESS_DENIED")
|
|
}
|
|
if role == "owner" {
|
|
return nil, fault(409, "OWNER_MEMORY_ACCESS_REQUIRED")
|
|
}
|
|
_, e = tx.Exec(ctx, `SELECT pg_advisory_xact_lock(hashtextextended($1,15))`, a.Subject+":"+user)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
var current int64
|
|
e = tx.QueryRow(ctx, `SELECT COALESCE((SELECT version FROM memory_access_grant WHERE subject_id=$1::uuid AND user_id=$2::uuid),0)`, a.Subject, user).Scan(¤t)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
if current != in.Version {
|
|
return nil, fault(409, "GRANT_VERSION_CONFLICT")
|
|
}
|
|
_, e = tx.Exec(ctx, `INSERT INTO memory_access_grant(subject_id,user_id,can_read,can_write,sensitive_read,version) VALUES($1::uuid,$2::uuid,$3,$4,$5,1) ON CONFLICT(subject_id,user_id) DO UPDATE SET can_read=excluded.can_read,can_write=excluded.can_write,sensitive_read=excluded.sensitive_read,version=memory_access_grant.version+1`, a.Subject, user, in.Read, in.Write, in.Sensitive)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
// Resolving a proposal does not preserve authority after this grant is revoked.
|
|
_, e = tx.Exec(ctx, `UPDATE memory_mutation SET confirmation_hash=NULL,proposal_digest=NULL,confirmation_expires_at=NULL,revision=revision+1 WHERE subject_id=$1::uuid AND actor_id=$2::uuid AND state='NEEDS_CONFIRMATION'`, a.Subject, user)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
}
|
|
var data []byte
|
|
e = tx.QueryRow(ctx, `SELECT COALESCE(jsonb_agg(jsonb_build_object('user_id',ua.public_id,'display_name',ua.display_name,'owner',hm.role='owner','can_read',hm.role='owner' OR COALESCE(g.can_read,false),'can_write',hm.role='owner' OR COALESCE(g.can_write,false),'sensitive_read',hm.role='owner' OR COALESCE(g.sensitive_read,false),'version',COALESCE(g.version,0)) ORDER BY ua.display_name),'[]'::jsonb) FROM subject_profile sp JOIN household_member hm ON hm.household_id=sp.household_id JOIN user_account ua ON ua.id=hm.user_id LEFT JOIN memory_access_grant g ON g.subject_id=sp.id AND g.user_id=ua.id WHERE sp.id=$1::uuid AND hm.status='active' AND ua.status='active'`, a.Subject).Scan(&data)
|
|
return map[string]any{"items": json.RawMessage(data)}, e
|
|
}
|