mirror of
https://github.com/1f349/lavender.git
synced 2024-12-22 07:34:06 +00:00
Closer to lavender v2
This commit is contained in:
parent
33c7ac9b06
commit
51e33322d3
10
Makefile
Normal file
10
Makefile
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
SQL_SRC_DIR := database
|
||||||
|
SQL_FILES := $(wildcard $(SQL_SRC_DIR)/{migrations,queries}/*.sql)
|
||||||
|
|
||||||
|
all: sqlc
|
||||||
|
|
||||||
|
sqlc: $(SQL_FILES)
|
||||||
|
sqlc generate
|
||||||
|
|
||||||
|
build: sqlc
|
||||||
|
go build ./cmd/lavender
|
@ -1,6 +1,10 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import "github.com/go-oauth2/oauth2/v4"
|
import (
|
||||||
|
"bufio"
|
||||||
|
"github.com/go-oauth2/oauth2/v4"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
var _ oauth2.ClientInfo = &ClientStore{}
|
var _ oauth2.ClientInfo = &ClientStore{}
|
||||||
|
|
||||||
@ -8,7 +12,7 @@ func (c *ClientStore) GetID() string { return c.Subject }
|
|||||||
func (c *ClientStore) GetSecret() string { return c.Secret }
|
func (c *ClientStore) GetSecret() string { return c.Secret }
|
||||||
func (c *ClientStore) GetDomain() string { return c.Domain }
|
func (c *ClientStore) GetDomain() string { return c.Domain }
|
||||||
func (c *ClientStore) IsPublic() bool { return c.Public }
|
func (c *ClientStore) IsPublic() bool { return c.Public }
|
||||||
func (c *ClientStore) GetUserID() string { return c.Owner }
|
func (c *ClientStore) GetUserID() string { return c.OwnerSubject }
|
||||||
|
|
||||||
// GetName is an extra field for the oauth handler to display the application
|
// GetName is an extra field for the oauth handler to display the application
|
||||||
// name
|
// name
|
||||||
@ -22,4 +26,12 @@ func (c *ClientStore) IsSSO() bool { return c.Sso }
|
|||||||
func (c *ClientStore) IsActive() bool { return c.Active }
|
func (c *ClientStore) IsActive() bool { return c.Active }
|
||||||
|
|
||||||
// UsePerms is an extra field for the userinfo handler to return user permissions matching the requested values
|
// UsePerms is an extra field for the userinfo handler to return user permissions matching the requested values
|
||||||
func (c *ClientStore) UsePerms() string { return c.Perms }
|
func (c *ClientStore) UsePerms() []string {
|
||||||
|
perms := make([]string, 0)
|
||||||
|
sc := bufio.NewScanner(strings.NewReader(c.Perms))
|
||||||
|
sc.Split(bufio.ScanWords)
|
||||||
|
if sc.Scan() {
|
||||||
|
perms = append(perms, sc.Text())
|
||||||
|
}
|
||||||
|
return perms
|
||||||
|
}
|
||||||
|
@ -10,32 +10,39 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const getAppList = `-- name: GetAppList :many
|
const getAppList = `-- name: GetAppList :many
|
||||||
SELECT subject, name, domain, owner, perms, public, sso, active
|
SELECT subject,
|
||||||
|
name,
|
||||||
|
domain,
|
||||||
|
owner_subject,
|
||||||
|
perms,
|
||||||
|
public,
|
||||||
|
sso,
|
||||||
|
active
|
||||||
FROM client_store
|
FROM client_store
|
||||||
WHERE owner = ?
|
WHERE owner_subject = ?
|
||||||
OR ? = 1
|
OR ? = 1
|
||||||
LIMIT 25 OFFSET ?
|
LIMIT 25 OFFSET ?
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetAppListParams struct {
|
type GetAppListParams struct {
|
||||||
Owner string `json:"owner"`
|
OwnerSubject string `json:"owner_subject"`
|
||||||
Column2 interface{} `json:"column_2"`
|
Column2 interface{} `json:"column_2"`
|
||||||
Offset int64 `json:"offset"`
|
Offset int64 `json:"offset"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetAppListRow struct {
|
type GetAppListRow struct {
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
Owner string `json:"owner"`
|
OwnerSubject string `json:"owner_subject"`
|
||||||
Perms string `json:"perms"`
|
Perms string `json:"perms"`
|
||||||
Public bool `json:"public"`
|
Public bool `json:"public"`
|
||||||
Sso bool `json:"sso"`
|
Sso bool `json:"sso"`
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetAppList(ctx context.Context, arg GetAppListParams) ([]GetAppListRow, error) {
|
func (q *Queries) GetAppList(ctx context.Context, arg GetAppListParams) ([]GetAppListRow, error) {
|
||||||
rows, err := q.db.QueryContext(ctx, getAppList, arg.Owner, arg.Column2, arg.Offset)
|
rows, err := q.db.QueryContext(ctx, getAppList, arg.OwnerSubject, arg.Column2, arg.Offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -47,7 +54,7 @@ func (q *Queries) GetAppList(ctx context.Context, arg GetAppListParams) ([]GetAp
|
|||||||
&i.Subject,
|
&i.Subject,
|
||||||
&i.Name,
|
&i.Name,
|
||||||
&i.Domain,
|
&i.Domain,
|
||||||
&i.Owner,
|
&i.OwnerSubject,
|
||||||
&i.Perms,
|
&i.Perms,
|
||||||
&i.Public,
|
&i.Public,
|
||||||
&i.Sso,
|
&i.Sso,
|
||||||
@ -67,7 +74,7 @@ func (q *Queries) GetAppList(ctx context.Context, arg GetAppListParams) ([]GetAp
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getClientInfo = `-- name: GetClientInfo :one
|
const getClientInfo = `-- name: GetClientInfo :one
|
||||||
SELECT subject, name, secret, domain, owner, perms, public, sso, active
|
SELECT subject, name, secret, domain, owner_subject, perms, public, sso, active
|
||||||
FROM client_store
|
FROM client_store
|
||||||
WHERE subject = ?
|
WHERE subject = ?
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
@ -81,7 +88,7 @@ func (q *Queries) GetClientInfo(ctx context.Context, subject string) (ClientStor
|
|||||||
&i.Name,
|
&i.Name,
|
||||||
&i.Secret,
|
&i.Secret,
|
||||||
&i.Domain,
|
&i.Domain,
|
||||||
&i.Owner,
|
&i.OwnerSubject,
|
||||||
&i.Perms,
|
&i.Perms,
|
||||||
&i.Public,
|
&i.Public,
|
||||||
&i.Sso,
|
&i.Sso,
|
||||||
@ -91,20 +98,20 @@ func (q *Queries) GetClientInfo(ctx context.Context, subject string) (ClientStor
|
|||||||
}
|
}
|
||||||
|
|
||||||
const insertClientApp = `-- name: InsertClientApp :exec
|
const insertClientApp = `-- name: InsertClientApp :exec
|
||||||
INSERT INTO client_store (subject, name, secret, domain, owner, perms, public, sso, active)
|
INSERT INTO client_store (subject, name, secret, domain, perms, public, sso, active, owner_subject)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`
|
`
|
||||||
|
|
||||||
type InsertClientAppParams struct {
|
type InsertClientAppParams struct {
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
Owner string `json:"owner"`
|
Perms string `json:"perms"`
|
||||||
Perms string `json:"perms"`
|
Public bool `json:"public"`
|
||||||
Public bool `json:"public"`
|
Sso bool `json:"sso"`
|
||||||
Sso bool `json:"sso"`
|
Active bool `json:"active"`
|
||||||
Active bool `json:"active"`
|
OwnerSubject string `json:"owner_subject"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) InsertClientApp(ctx context.Context, arg InsertClientAppParams) error {
|
func (q *Queries) InsertClientApp(ctx context.Context, arg InsertClientAppParams) error {
|
||||||
@ -113,11 +120,11 @@ func (q *Queries) InsertClientApp(ctx context.Context, arg InsertClientAppParams
|
|||||||
arg.Name,
|
arg.Name,
|
||||||
arg.Secret,
|
arg.Secret,
|
||||||
arg.Domain,
|
arg.Domain,
|
||||||
arg.Owner,
|
|
||||||
arg.Perms,
|
arg.Perms,
|
||||||
arg.Public,
|
arg.Public,
|
||||||
arg.Sso,
|
arg.Sso,
|
||||||
arg.Active,
|
arg.Active,
|
||||||
|
arg.OwnerSubject,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -126,17 +133,17 @@ const resetClientAppSecret = `-- name: ResetClientAppSecret :exec
|
|||||||
UPDATE client_store
|
UPDATE client_store
|
||||||
SET secret = ?
|
SET secret = ?
|
||||||
WHERE subject = ?
|
WHERE subject = ?
|
||||||
AND owner = ?
|
AND owner_subject = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
type ResetClientAppSecretParams struct {
|
type ResetClientAppSecretParams struct {
|
||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
Owner string `json:"owner"`
|
OwnerSubject string `json:"owner_subject"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ResetClientAppSecret(ctx context.Context, arg ResetClientAppSecretParams) error {
|
func (q *Queries) ResetClientAppSecret(ctx context.Context, arg ResetClientAppSecretParams) error {
|
||||||
_, err := q.db.ExecContext(ctx, resetClientAppSecret, arg.Secret, arg.Subject, arg.Owner)
|
_, err := q.db.ExecContext(ctx, resetClientAppSecret, arg.Secret, arg.Subject, arg.OwnerSubject)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,19 +156,19 @@ SET name = ?,
|
|||||||
sso = ?,
|
sso = ?,
|
||||||
active = ?
|
active = ?
|
||||||
WHERE subject = ?
|
WHERE subject = ?
|
||||||
AND owner = ?
|
AND owner_subject = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateClientAppParams struct {
|
type UpdateClientAppParams struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
Column3 bool `json:"column_3"`
|
Column3 bool `json:"column_3"`
|
||||||
Perms string `json:"perms"`
|
Perms string `json:"perms"`
|
||||||
Public bool `json:"public"`
|
Public bool `json:"public"`
|
||||||
Sso bool `json:"sso"`
|
Sso bool `json:"sso"`
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
Owner string `json:"owner"`
|
OwnerSubject string `json:"owner_subject"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateClientApp(ctx context.Context, arg UpdateClientAppParams) error {
|
func (q *Queries) UpdateClientApp(ctx context.Context, arg UpdateClientAppParams) error {
|
||||||
@ -174,7 +181,7 @@ func (q *Queries) UpdateClientApp(ctx context.Context, arg UpdateClientAppParams
|
|||||||
arg.Sso,
|
arg.Sso,
|
||||||
arg.Active,
|
arg.Active,
|
||||||
arg.Subject,
|
arg.Subject,
|
||||||
arg.Owner,
|
arg.OwnerSubject,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -7,27 +7,51 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const changeUserActive = `-- name: ChangeUserActive :exec
|
||||||
|
UPDATE users
|
||||||
|
SET active = cast(? as boolean)
|
||||||
|
WHERE subject = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
type ChangeUserActiveParams struct {
|
||||||
|
Column1 bool `json:"column_1"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ChangeUserActive(ctx context.Context, arg ChangeUserActiveParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, changeUserActive, arg.Column1, arg.Subject)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const getUserList = `-- name: GetUserList :many
|
const getUserList = `-- name: GetUserList :many
|
||||||
SELECT subject,
|
SELECT users.subject,
|
||||||
|
name,
|
||||||
|
picture,
|
||||||
|
website,
|
||||||
email,
|
email,
|
||||||
email_verified,
|
email_verified,
|
||||||
roles,
|
users.updated_at as user_updated_at,
|
||||||
updated_at,
|
p.updated_at as profile_updated_at,
|
||||||
active
|
active
|
||||||
FROM users
|
FROM users
|
||||||
LIMIT 25 OFFSET ?
|
INNER JOIN main.profiles p on users.subject = p.subject
|
||||||
|
LIMIT 50 OFFSET ?
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetUserListRow struct {
|
type GetUserListRow struct {
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
Email string `json:"email"`
|
Name string `json:"name"`
|
||||||
EmailVerified bool `json:"email_verified"`
|
Picture string `json:"picture"`
|
||||||
Roles string `json:"roles"`
|
Website string `json:"website"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
Email string `json:"email"`
|
||||||
Active bool `json:"active"`
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
UserUpdatedAt time.Time `json:"user_updated_at"`
|
||||||
|
ProfileUpdatedAt time.Time `json:"profile_updated_at"`
|
||||||
|
Active bool `json:"active"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetUserList(ctx context.Context, offset int64) ([]GetUserListRow, error) {
|
func (q *Queries) GetUserList(ctx context.Context, offset int64) ([]GetUserListRow, error) {
|
||||||
@ -41,10 +65,13 @@ func (q *Queries) GetUserList(ctx context.Context, offset int64) ([]GetUserListR
|
|||||||
var i GetUserListRow
|
var i GetUserListRow
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.Subject,
|
&i.Subject,
|
||||||
|
&i.Name,
|
||||||
|
&i.Picture,
|
||||||
|
&i.Website,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.EmailVerified,
|
&i.EmailVerified,
|
||||||
&i.Roles,
|
&i.UserUpdatedAt,
|
||||||
&i.UpdatedAt,
|
&i.ProfileUpdatedAt,
|
||||||
&i.Active,
|
&i.Active,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -60,22 +87,50 @@ func (q *Queries) GetUserList(ctx context.Context, offset int64) ([]GetUserListR
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateUser = `-- name: UpdateUser :exec
|
const getUsersRoles = `-- name: GetUsersRoles :many
|
||||||
UPDATE users
|
SELECT r.role, u.id
|
||||||
SET active = ?,
|
FROM users_roles
|
||||||
roles=?
|
INNER JOIN roles r on r.id = users_roles.role_id
|
||||||
WHERE subject = ?
|
INNER JOIN users u on u.id = users_roles.user_id
|
||||||
|
WHERE u.id in /*SLICE:user_ids*/?
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateUserParams struct {
|
type GetUsersRolesRow struct {
|
||||||
Active bool `json:"active"`
|
Role string `json:"role"`
|
||||||
Roles string `json:"roles"`
|
ID int64 `json:"id"`
|
||||||
Subject string `json:"subject"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
|
func (q *Queries) GetUsersRoles(ctx context.Context, userIds []int64) ([]GetUsersRolesRow, error) {
|
||||||
_, err := q.db.ExecContext(ctx, updateUser, arg.Active, arg.Roles, arg.Subject)
|
query := getUsersRoles
|
||||||
return err
|
var queryParams []interface{}
|
||||||
|
if len(userIds) > 0 {
|
||||||
|
for _, v := range userIds {
|
||||||
|
queryParams = append(queryParams, v)
|
||||||
|
}
|
||||||
|
query = strings.Replace(query, "/*SLICE:user_ids*/?", strings.Repeat(",?", len(userIds))[1:], 1)
|
||||||
|
} else {
|
||||||
|
query = strings.Replace(query, "/*SLICE:user_ids*/?", "NULL", 1)
|
||||||
|
}
|
||||||
|
rows, err := q.db.QueryContext(ctx, query, queryParams...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetUsersRolesRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetUsersRolesRow
|
||||||
|
if err := rows.Scan(&i.Role, &i.ID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const userEmailExists = `-- name: UserEmailExists :one
|
const userEmailExists = `-- name: UserEmailExists :one
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
DROP TABLE users;
|
|
||||||
DROP TABLE client_store;
|
|
@ -1,27 +0,0 @@
|
|||||||
CREATE TABLE users
|
|
||||||
(
|
|
||||||
subject TEXT PRIMARY KEY UNIQUE NOT NULL,
|
|
||||||
email TEXT UNIQUE NOT NULL,
|
|
||||||
email_verified BOOLEAN DEFAULT 0 NOT NULL,
|
|
||||||
roles TEXT NOT NULL,
|
|
||||||
userinfo TEXT NOT NULL,
|
|
||||||
access_token TEXT,
|
|
||||||
refresh_token TEXT,
|
|
||||||
expiry DATETIME,
|
|
||||||
updated_at DATETIME NOT NULL,
|
|
||||||
active BOOLEAN DEFAULT 1 NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE client_store
|
|
||||||
(
|
|
||||||
subject TEXT PRIMARY KEY UNIQUE NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
secret TEXT UNIQUE NOT NULL,
|
|
||||||
domain TEXT NOT NULL,
|
|
||||||
owner TEXT NOT NULL,
|
|
||||||
perms TEXT NOT NULL,
|
|
||||||
public BOOLEAN NOT NULL,
|
|
||||||
sso BOOLEAN NOT NULL,
|
|
||||||
active BOOLEAN DEFAULT 1 NOT NULL,
|
|
||||||
FOREIGN KEY (owner) REFERENCES users (subject)
|
|
||||||
);
|
|
5
database/migrations/20240820202502_init.down.sql
Normal file
5
database/migrations/20240820202502_init.down.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
DROP TABLE users;
|
||||||
|
DROP INDEX username_index;
|
||||||
|
DROP TABLE roles;
|
||||||
|
DROP TABLE otp;
|
||||||
|
DROP TABLE client_store;
|
69
database/migrations/20240820202502_init.up.sql
Normal file
69
database/migrations/20240820202502_init.up.sql
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
CREATE TABLE users
|
||||||
|
(
|
||||||
|
id INTEGER NOT NULL UNIQUE PRIMARY KEY AUTOINCREMENT,
|
||||||
|
subject TEXT NOT NULL UNIQUE,
|
||||||
|
password TEXT NOT NULL,
|
||||||
|
|
||||||
|
email TEXT NOT NULL,
|
||||||
|
email_verified BOOLEAN NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
updated_at DATETIME NOT NULL,
|
||||||
|
registered DATETIME NOT NULL,
|
||||||
|
active BOOLEAN NOT NULL DEFAULT 1
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX users_subject ON users (subject);
|
||||||
|
|
||||||
|
CREATE TABLE profiles
|
||||||
|
(
|
||||||
|
subject TEXT NOT NULL UNIQUE PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
picture TEXT NOT NULL DEFAULT '',
|
||||||
|
website TEXT NOT NULL DEFAULT '',
|
||||||
|
pronouns TEXT NOT NULL DEFAULT 'they/them',
|
||||||
|
birthdate DATE NULL,
|
||||||
|
zone TEXT NOT NULL DEFAULT 'UTC',
|
||||||
|
locale TEXT NOT NULL DEFAULT 'en-US',
|
||||||
|
updated_at DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE roles
|
||||||
|
(
|
||||||
|
id INTEGER NOT NULL UNIQUE PRIMARY KEY AUTOINCREMENT,
|
||||||
|
role TEXT NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE users_roles
|
||||||
|
(
|
||||||
|
role_id INTEGER NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY (role_id) REFERENCES roles (id),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users (id),
|
||||||
|
|
||||||
|
CONSTRAINT user_role UNIQUE (role_id, user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE otp
|
||||||
|
(
|
||||||
|
subject INTEGER NOT NULL UNIQUE PRIMARY KEY,
|
||||||
|
secret TEXT NOT NULL,
|
||||||
|
digits INTEGER NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY (subject) REFERENCES users (subject)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE client_store
|
||||||
|
(
|
||||||
|
subject TEXT NOT NULL UNIQUE PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
secret TEXT NOT NULL UNIQUE,
|
||||||
|
domain TEXT NOT NULL,
|
||||||
|
owner_subject TEXT NOT NULL,
|
||||||
|
perms TEXT NOT NULL,
|
||||||
|
public BOOLEAN NOT NULL,
|
||||||
|
sso BOOLEAN NOT NULL,
|
||||||
|
active BOOLEAN NOT NULL DEFAULT 1,
|
||||||
|
|
||||||
|
FOREIGN KEY (owner_subject) REFERENCES users (subject)
|
||||||
|
);
|
@ -5,31 +5,58 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/1f349/lavender/password"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientStore struct {
|
type ClientStore struct {
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
OwnerSubject string `json:"owner_subject"`
|
||||||
|
Perms string `json:"perms"`
|
||||||
|
Public bool `json:"public"`
|
||||||
|
Sso bool `json:"sso"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Otp struct {
|
||||||
|
Subject int64 `json:"subject"`
|
||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
Domain string `json:"domain"`
|
Digits int64 `json:"digits"`
|
||||||
Owner string `json:"owner"`
|
}
|
||||||
Perms string `json:"perms"`
|
|
||||||
Public bool `json:"public"`
|
type Profile struct {
|
||||||
Sso bool `json:"sso"`
|
Subject string `json:"subject"`
|
||||||
Active bool `json:"active"`
|
Name string `json:"name"`
|
||||||
|
Picture string `json:"picture"`
|
||||||
|
Website string `json:"website"`
|
||||||
|
Pronouns string `json:"pronouns"`
|
||||||
|
Birthdate interface{} `json:"birthdate"`
|
||||||
|
Zone string `json:"zone"`
|
||||||
|
Locale string `json:"locale"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Role struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Role string `json:"role"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Subject string `json:"subject"`
|
ID int64 `json:"id"`
|
||||||
Email string `json:"email"`
|
Subject string `json:"subject"`
|
||||||
EmailVerified bool `json:"email_verified"`
|
Password password.HashString `json:"password"`
|
||||||
Roles string `json:"roles"`
|
Email string `json:"email"`
|
||||||
Userinfo string `json:"userinfo"`
|
EmailVerified bool `json:"email_verified"`
|
||||||
AccessToken sql.NullString `json:"access_token"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
RefreshToken sql.NullString `json:"refresh_token"`
|
Registered time.Time `json:"registered"`
|
||||||
Expiry sql.NullTime `json:"expiry"`
|
Active bool `json:"active"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
}
|
||||||
Active bool `json:"active"`
|
|
||||||
|
type UsersRole struct {
|
||||||
|
RoleID int64 `json:"role_id"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
}
|
}
|
||||||
|
81
database/otp.sql.go
Normal file
81
database/otp.sql.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.25.0
|
||||||
|
// source: otp.sql
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const deleteOtp = `-- name: DeleteOtp :exec
|
||||||
|
DELETE
|
||||||
|
FROM otp
|
||||||
|
WHERE otp.subject = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteOtp(ctx context.Context, subject int64) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteOtp, subject)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOtp = `-- name: GetOtp :one
|
||||||
|
SELECT secret, digits
|
||||||
|
FROM otp
|
||||||
|
WHERE subject = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetOtpRow struct {
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
Digits int64 `json:"digits"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetOtp(ctx context.Context, subject int64) (GetOtpRow, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getOtp, subject)
|
||||||
|
var i GetOtpRow
|
||||||
|
err := row.Scan(&i.Secret, &i.Digits)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserEmail = `-- name: GetUserEmail :one
|
||||||
|
SELECT email
|
||||||
|
FROM users
|
||||||
|
WHERE subject = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetUserEmail(ctx context.Context, subject string) (string, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getUserEmail, subject)
|
||||||
|
var email string
|
||||||
|
err := row.Scan(&email)
|
||||||
|
return email, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasOtp = `-- name: HasOtp :one
|
||||||
|
SELECT EXISTS(SELECT 1 FROM otp WHERE subject = ?) == 1 as hasOtp
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) HasOtp(ctx context.Context, subject int64) (bool, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, hasOtp, subject)
|
||||||
|
var hasotp bool
|
||||||
|
err := row.Scan(&hasotp)
|
||||||
|
return hasotp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const setOtp = `-- name: SetOtp :exec
|
||||||
|
INSERT OR
|
||||||
|
REPLACE
|
||||||
|
INTO otp (subject, secret, digits)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
`
|
||||||
|
|
||||||
|
type SetOtpParams struct {
|
||||||
|
Subject int64 `json:"subject"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
Digits int64 `json:"digits"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) SetOtp(ctx context.Context, arg SetOtpParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, setOtp, arg.Subject, arg.Secret, arg.Digits)
|
||||||
|
return err
|
||||||
|
}
|
78
database/password-wrapper.go
Normal file
78
database/password-wrapper.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/1f349/lavender/database/types"
|
||||||
|
"github.com/1f349/lavender/password"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AddUserParams struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Role types.UserRole `json:"role"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) AddUser(ctx context.Context, arg AddUserParams) (string, error) {
|
||||||
|
pwHash, err := password.HashPassword(arg.Password)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
n := time.Now()
|
||||||
|
a := addUserParams{
|
||||||
|
Subject: uuid.NewString(),
|
||||||
|
Password: pwHash,
|
||||||
|
Email: arg.Email,
|
||||||
|
EmailVerified: false,
|
||||||
|
UpdatedAt: n,
|
||||||
|
Registered: n,
|
||||||
|
Active: true,
|
||||||
|
}
|
||||||
|
return a.Subject, q.addUser(ctx, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckLoginResult struct {
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
HasOtp bool `json:"has_otp"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CheckLogin(ctx context.Context, un, pw string) (CheckLoginResult, error) {
|
||||||
|
login, err := q.checkLogin(ctx, un)
|
||||||
|
if err != nil {
|
||||||
|
return CheckLoginResult{}, err
|
||||||
|
}
|
||||||
|
err = password.CheckPasswordHash(login.Password, pw)
|
||||||
|
if err != nil {
|
||||||
|
return CheckLoginResult{}, err
|
||||||
|
}
|
||||||
|
return CheckLoginResult{
|
||||||
|
Subject: login.Subject,
|
||||||
|
HasOtp: login.HasOtp,
|
||||||
|
Email: login.Email,
|
||||||
|
EmailVerified: login.EmailVerified,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ChangePassword(ctx context.Context, subject, newPw string) error {
|
||||||
|
userPassword, err := q.getUserPassword(ctx, subject)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newPwHash, err := password.HashPassword(newPw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return q.changeUserPassword(ctx, changeUserPasswordParams{
|
||||||
|
Password: newPwHash,
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
Subject: subject,
|
||||||
|
Password_2: userPassword,
|
||||||
|
})
|
||||||
|
}
|
62
database/profile-patch.go
Normal file
62
database/profile-patch.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/1f349/lavender/database/types"
|
||||||
|
"github.com/hardfinhq/go-date"
|
||||||
|
"github.com/mrmelon54/pronouns"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProfilePatch struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Picture string `json:"picture"`
|
||||||
|
Website string `json:"website"`
|
||||||
|
Pronouns types.UserPronoun `json:"pronouns"`
|
||||||
|
Birthdate date.NullDate `json:"birthdate"`
|
||||||
|
Zone types.UserZone `json:"zone"`
|
||||||
|
Locale types.UserLocale `json:"locale"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ProfilePatch) ParseFromForm(v url.Values) (safeErrs []error) {
|
||||||
|
var err error
|
||||||
|
p.Name = v.Get("name")
|
||||||
|
p.Picture = v.Get("picture")
|
||||||
|
p.Website = v.Get("website")
|
||||||
|
if v.Has("reset_pronouns") {
|
||||||
|
p.Pronouns.Pronoun = pronouns.TheyThem
|
||||||
|
} else {
|
||||||
|
p.Pronouns.Pronoun, err = pronouns.FindPronoun(v.Get("pronouns"))
|
||||||
|
if err != nil {
|
||||||
|
safeErrs = append(safeErrs, fmt.Errorf("invalid pronoun selected"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v.Has("reset_birthdate") || v.Get("birthdate") == "" {
|
||||||
|
p.Birthdate = date.NullDate{}
|
||||||
|
} else {
|
||||||
|
p.Birthdate = date.NullDate{Valid: true}
|
||||||
|
p.Birthdate.Date, err = date.FromString(v.Get("birthdate"))
|
||||||
|
if err != nil {
|
||||||
|
safeErrs = append(safeErrs, fmt.Errorf("invalid time selected"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v.Has("reset_zoneinfo") {
|
||||||
|
p.Zone.Location = time.UTC
|
||||||
|
} else {
|
||||||
|
p.Zone.Location, err = time.LoadLocation(v.Get("zoneinfo"))
|
||||||
|
if err != nil {
|
||||||
|
safeErrs = append(safeErrs, fmt.Errorf("invalid timezone selected"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v.Has("reset_locale") {
|
||||||
|
p.Locale.Tag = language.AmericanEnglish
|
||||||
|
} else {
|
||||||
|
p.Locale.Tag, err = language.Parse(v.Get("locale"))
|
||||||
|
if err != nil {
|
||||||
|
safeErrs = append(safeErrs, fmt.Errorf("invalid language selected"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
74
database/profiles.sql.go
Normal file
74
database/profiles.sql.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.25.0
|
||||||
|
// source: profiles.sql
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const getProfile = `-- name: GetProfile :one
|
||||||
|
SELECT profiles.subject, profiles.name, profiles.picture, profiles.website, profiles.pronouns, profiles.birthdate, profiles.zone, profiles.locale, profiles.updated_at
|
||||||
|
FROM profiles
|
||||||
|
WHERE subject = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetProfile(ctx context.Context, subject string) (Profile, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getProfile, subject)
|
||||||
|
var i Profile
|
||||||
|
err := row.Scan(
|
||||||
|
&i.Subject,
|
||||||
|
&i.Name,
|
||||||
|
&i.Picture,
|
||||||
|
&i.Website,
|
||||||
|
&i.Pronouns,
|
||||||
|
&i.Birthdate,
|
||||||
|
&i.Zone,
|
||||||
|
&i.Locale,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifyProfile = `-- name: ModifyProfile :exec
|
||||||
|
UPDATE profiles
|
||||||
|
SET name = ?,
|
||||||
|
picture = ?,
|
||||||
|
website = ?,
|
||||||
|
pronouns = ?,
|
||||||
|
birthdate = ?,
|
||||||
|
zone = ?,
|
||||||
|
locale = ?,
|
||||||
|
updated_at = ?
|
||||||
|
WHERE subject = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
type ModifyProfileParams struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Picture string `json:"picture"`
|
||||||
|
Website string `json:"website"`
|
||||||
|
Pronouns string `json:"pronouns"`
|
||||||
|
Birthdate interface{} `json:"birthdate"`
|
||||||
|
Zone string `json:"zone"`
|
||||||
|
Locale string `json:"locale"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ModifyProfile(ctx context.Context, arg ModifyProfileParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, modifyProfile,
|
||||||
|
arg.Name,
|
||||||
|
arg.Picture,
|
||||||
|
arg.Website,
|
||||||
|
arg.Pronouns,
|
||||||
|
arg.Birthdate,
|
||||||
|
arg.Zone,
|
||||||
|
arg.Locale,
|
||||||
|
arg.UpdatedAt,
|
||||||
|
arg.Subject,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
@ -5,14 +5,21 @@ WHERE subject = ?
|
|||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
|
|
||||||
-- name: GetAppList :many
|
-- name: GetAppList :many
|
||||||
SELECT subject, name, domain, owner, perms, public, sso, active
|
SELECT subject,
|
||||||
|
name,
|
||||||
|
domain,
|
||||||
|
owner_subject,
|
||||||
|
perms,
|
||||||
|
public,
|
||||||
|
sso,
|
||||||
|
active
|
||||||
FROM client_store
|
FROM client_store
|
||||||
WHERE owner = ?
|
WHERE owner_subject = ?
|
||||||
OR ? = 1
|
OR ? = 1
|
||||||
LIMIT 25 OFFSET ?;
|
LIMIT 25 OFFSET ?;
|
||||||
|
|
||||||
-- name: InsertClientApp :exec
|
-- name: InsertClientApp :exec
|
||||||
INSERT INTO client_store (subject, name, secret, domain, owner, perms, public, sso, active)
|
INSERT INTO client_store (subject, name, secret, domain, perms, public, sso, active, owner_subject)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||||
|
|
||||||
-- name: UpdateClientApp :exec
|
-- name: UpdateClientApp :exec
|
||||||
@ -24,10 +31,10 @@ SET name = ?,
|
|||||||
sso = ?,
|
sso = ?,
|
||||||
active = ?
|
active = ?
|
||||||
WHERE subject = ?
|
WHERE subject = ?
|
||||||
AND owner = ?;
|
AND owner_subject = ?;
|
||||||
|
|
||||||
-- name: ResetClientAppSecret :exec
|
-- name: ResetClientAppSecret :exec
|
||||||
UPDATE client_store
|
UPDATE client_store
|
||||||
SET secret = ?
|
SET secret = ?
|
||||||
WHERE subject = ?
|
WHERE subject = ?
|
||||||
AND owner = ?;
|
AND owner_subject = ?;
|
||||||
|
@ -1,17 +1,27 @@
|
|||||||
-- name: GetUserList :many
|
-- name: GetUserList :many
|
||||||
SELECT subject,
|
SELECT users.subject,
|
||||||
|
name,
|
||||||
|
picture,
|
||||||
|
website,
|
||||||
email,
|
email,
|
||||||
email_verified,
|
email_verified,
|
||||||
roles,
|
users.updated_at as user_updated_at,
|
||||||
updated_at,
|
p.updated_at as profile_updated_at,
|
||||||
active
|
active
|
||||||
FROM users
|
FROM users
|
||||||
LIMIT 25 OFFSET ?;
|
INNER JOIN main.profiles p on users.subject = p.subject
|
||||||
|
LIMIT 50 OFFSET ?;
|
||||||
|
|
||||||
-- name: UpdateUser :exec
|
-- name: GetUsersRoles :many
|
||||||
|
SELECT r.role, u.id
|
||||||
|
FROM users_roles
|
||||||
|
INNER JOIN roles r on r.id = users_roles.role_id
|
||||||
|
INNER JOIN users u on u.id = users_roles.user_id
|
||||||
|
WHERE u.id in sqlc.slice(user_ids);
|
||||||
|
|
||||||
|
-- name: ChangeUserActive :exec
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET active = ?,
|
SET active = cast(? as boolean)
|
||||||
roles=?
|
|
||||||
WHERE subject = ?;
|
WHERE subject = ?;
|
||||||
|
|
||||||
-- name: UserEmailExists :one
|
-- name: UserEmailExists :one
|
||||||
|
23
database/queries/otp.sql
Normal file
23
database/queries/otp.sql
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
-- name: SetOtp :exec
|
||||||
|
INSERT OR
|
||||||
|
REPLACE
|
||||||
|
INTO otp (subject, secret, digits)
|
||||||
|
VALUES (?, ?, ?);
|
||||||
|
|
||||||
|
-- name: DeleteOtp :exec
|
||||||
|
DELETE
|
||||||
|
FROM otp
|
||||||
|
WHERE otp.subject = ?;
|
||||||
|
|
||||||
|
-- name: GetOtp :one
|
||||||
|
SELECT secret, digits
|
||||||
|
FROM otp
|
||||||
|
WHERE subject = ?;
|
||||||
|
|
||||||
|
-- name: HasOtp :one
|
||||||
|
SELECT EXISTS(SELECT 1 FROM otp WHERE subject = ?) == 1 as hasOtp;
|
||||||
|
|
||||||
|
-- name: GetUserEmail :one
|
||||||
|
SELECT email
|
||||||
|
FROM users
|
||||||
|
WHERE subject = ?;
|
16
database/queries/profiles.sql
Normal file
16
database/queries/profiles.sql
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
-- name: GetProfile :one
|
||||||
|
SELECT profiles.*
|
||||||
|
FROM profiles
|
||||||
|
WHERE subject = ?;
|
||||||
|
|
||||||
|
-- name: ModifyProfile :exec
|
||||||
|
UPDATE profiles
|
||||||
|
SET name = ?,
|
||||||
|
picture = ?,
|
||||||
|
website = ?,
|
||||||
|
pronouns = ?,
|
||||||
|
birthdate = ?,
|
||||||
|
zone = ?,
|
||||||
|
locale = ?,
|
||||||
|
updated_at = ?
|
||||||
|
WHERE subject = ?;
|
@ -2,21 +2,15 @@
|
|||||||
SELECT count(subject) > 0 AS hasUser
|
SELECT count(subject) > 0 AS hasUser
|
||||||
FROM users;
|
FROM users;
|
||||||
|
|
||||||
-- name: AddUser :exec
|
-- name: addUser :exec
|
||||||
INSERT INTO users (subject, email, email_verified, roles, userinfo, updated_at, active)
|
INSERT INTO users (subject, password, email, email_verified, updated_at, registered, active)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?);
|
VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||||
|
|
||||||
-- name: UpdateUserInfo :exec
|
-- name: checkLogin :one
|
||||||
UPDATE users
|
SELECT subject, password, EXISTS(SELECT 1 FROM otp WHERE otp.subject = users.subject) == 1 AS has_otp, email, email_verified
|
||||||
SET email = ?,
|
|
||||||
email_verified = ?,
|
|
||||||
userinfo = ?
|
|
||||||
WHERE subject = ?;
|
|
||||||
|
|
||||||
-- name: GetUserRoles :one
|
|
||||||
SELECT roles
|
|
||||||
FROM users
|
FROM users
|
||||||
WHERE subject = ?;
|
WHERE users.subject = ?
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
-- name: GetUser :one
|
-- name: GetUser :one
|
||||||
SELECT *
|
SELECT *
|
||||||
@ -24,20 +18,29 @@ FROM users
|
|||||||
WHERE subject = ?
|
WHERE subject = ?
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
|
|
||||||
-- name: UpdateUserToken :exec
|
-- name: GetUserRoles :many
|
||||||
|
SELECT r.role
|
||||||
|
FROM users_roles
|
||||||
|
INNER JOIN roles r on r.id = users_roles.role_id
|
||||||
|
INNER JOIN users u on u.id = users_roles.user_id
|
||||||
|
WHERE u.subject = ?;
|
||||||
|
|
||||||
|
-- name: UserHasRole :one
|
||||||
|
SELECT 1
|
||||||
|
FROM roles
|
||||||
|
INNER JOIN users_roles on users_roles.user_id = roles.id
|
||||||
|
INNER JOIN users u on u.id = users_roles.user_id = u.id
|
||||||
|
WHERE roles.role = ?
|
||||||
|
AND u.subject = ?;
|
||||||
|
|
||||||
|
-- name: getUserPassword :one
|
||||||
|
SELECT password
|
||||||
|
FROM users
|
||||||
|
WHERE subject = ?;
|
||||||
|
|
||||||
|
-- name: changeUserPassword :exec
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET access_token = ?,
|
SET password = ?,
|
||||||
refresh_token = ?,
|
updated_at=?
|
||||||
expiry = ?
|
|
||||||
WHERE subject = ?;
|
|
||||||
|
|
||||||
-- name: GetUserToken :one
|
|
||||||
SELECT access_token, refresh_token, expiry
|
|
||||||
FROM users
|
|
||||||
WHERE subject = ?
|
WHERE subject = ?
|
||||||
LIMIT 1;
|
AND password = ?;
|
||||||
|
|
||||||
-- name: GetUserEmail :one
|
|
||||||
SELECT email
|
|
||||||
FROM users
|
|
||||||
WHERE subject = ?;
|
|
||||||
|
@ -7,40 +7,13 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/1f349/lavender/password"
|
||||||
)
|
)
|
||||||
|
|
||||||
const addUser = `-- name: AddUser :exec
|
|
||||||
INSERT INTO users (subject, email, email_verified, roles, userinfo, updated_at, active)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
||||||
`
|
|
||||||
|
|
||||||
type AddUserParams struct {
|
|
||||||
Subject string `json:"subject"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
EmailVerified bool `json:"email_verified"`
|
|
||||||
Roles string `json:"roles"`
|
|
||||||
Userinfo string `json:"userinfo"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
|
||||||
Active bool `json:"active"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) AddUser(ctx context.Context, arg AddUserParams) error {
|
|
||||||
_, err := q.db.ExecContext(ctx, addUser,
|
|
||||||
arg.Subject,
|
|
||||||
arg.Email,
|
|
||||||
arg.EmailVerified,
|
|
||||||
arg.Roles,
|
|
||||||
arg.Userinfo,
|
|
||||||
arg.UpdatedAt,
|
|
||||||
arg.Active,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const getUser = `-- name: GetUser :one
|
const getUser = `-- name: GetUser :one
|
||||||
SELECT subject, email, email_verified, roles, userinfo, access_token, refresh_token, expiry, updated_at, active
|
SELECT id, subject, password, email, email_verified, updated_at, registered, active
|
||||||
FROM users
|
FROM users
|
||||||
WHERE subject = ?
|
WHERE subject = ?
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
@ -50,64 +23,47 @@ func (q *Queries) GetUser(ctx context.Context, subject string) (User, error) {
|
|||||||
row := q.db.QueryRowContext(ctx, getUser, subject)
|
row := q.db.QueryRowContext(ctx, getUser, subject)
|
||||||
var i User
|
var i User
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
&i.Subject,
|
&i.Subject,
|
||||||
|
&i.Password,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.EmailVerified,
|
&i.EmailVerified,
|
||||||
&i.Roles,
|
|
||||||
&i.Userinfo,
|
|
||||||
&i.AccessToken,
|
|
||||||
&i.RefreshToken,
|
|
||||||
&i.Expiry,
|
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
&i.Registered,
|
||||||
&i.Active,
|
&i.Active,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getUserEmail = `-- name: GetUserEmail :one
|
const getUserRoles = `-- name: GetUserRoles :many
|
||||||
SELECT email
|
SELECT r.role
|
||||||
FROM users
|
FROM users_roles
|
||||||
WHERE subject = ?
|
INNER JOIN roles r on r.id = users_roles.role_id
|
||||||
|
INNER JOIN users u on u.id = users_roles.user_id
|
||||||
|
WHERE u.subject = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetUserEmail(ctx context.Context, subject string) (string, error) {
|
func (q *Queries) GetUserRoles(ctx context.Context, subject string) ([]string, error) {
|
||||||
row := q.db.QueryRowContext(ctx, getUserEmail, subject)
|
rows, err := q.db.QueryContext(ctx, getUserRoles, subject)
|
||||||
var email string
|
if err != nil {
|
||||||
err := row.Scan(&email)
|
return nil, err
|
||||||
return email, err
|
}
|
||||||
}
|
defer rows.Close()
|
||||||
|
var items []string
|
||||||
const getUserRoles = `-- name: GetUserRoles :one
|
for rows.Next() {
|
||||||
SELECT roles
|
var role string
|
||||||
FROM users
|
if err := rows.Scan(&role); err != nil {
|
||||||
WHERE subject = ?
|
return nil, err
|
||||||
`
|
}
|
||||||
|
items = append(items, role)
|
||||||
func (q *Queries) GetUserRoles(ctx context.Context, subject string) (string, error) {
|
}
|
||||||
row := q.db.QueryRowContext(ctx, getUserRoles, subject)
|
if err := rows.Close(); err != nil {
|
||||||
var roles string
|
return nil, err
|
||||||
err := row.Scan(&roles)
|
}
|
||||||
return roles, err
|
if err := rows.Err(); err != nil {
|
||||||
}
|
return nil, err
|
||||||
|
}
|
||||||
const getUserToken = `-- name: GetUserToken :one
|
return items, nil
|
||||||
SELECT access_token, refresh_token, expiry
|
|
||||||
FROM users
|
|
||||||
WHERE subject = ?
|
|
||||||
LIMIT 1
|
|
||||||
`
|
|
||||||
|
|
||||||
type GetUserTokenRow struct {
|
|
||||||
AccessToken sql.NullString `json:"access_token"`
|
|
||||||
RefreshToken sql.NullString `json:"refresh_token"`
|
|
||||||
Expiry sql.NullTime `json:"expiry"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetUserToken(ctx context.Context, subject string) (GetUserTokenRow, error) {
|
|
||||||
row := q.db.QueryRowContext(ctx, getUserToken, subject)
|
|
||||||
var i GetUserTokenRow
|
|
||||||
err := row.Scan(&i.AccessToken, &i.RefreshToken, &i.Expiry)
|
|
||||||
return i, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasUser = `-- name: HasUser :one
|
const hasUser = `-- name: HasUser :one
|
||||||
@ -122,52 +78,117 @@ func (q *Queries) HasUser(ctx context.Context) (bool, error) {
|
|||||||
return hasuser, err
|
return hasuser, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateUserInfo = `-- name: UpdateUserInfo :exec
|
const userHasRole = `-- name: UserHasRole :one
|
||||||
UPDATE users
|
SELECT 1
|
||||||
SET email = ?,
|
FROM roles
|
||||||
email_verified = ?,
|
INNER JOIN users_roles on users_roles.user_id = roles.id
|
||||||
userinfo = ?
|
INNER JOIN users u on u.id = users_roles.user_id = u.id
|
||||||
WHERE subject = ?
|
WHERE roles.role = ?
|
||||||
|
AND u.subject = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateUserInfoParams struct {
|
type UserHasRoleParams struct {
|
||||||
Email string `json:"email"`
|
Role string `json:"role"`
|
||||||
EmailVerified bool `json:"email_verified"`
|
Subject string `json:"subject"`
|
||||||
Userinfo string `json:"userinfo"`
|
|
||||||
Subject string `json:"subject"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateUserInfo(ctx context.Context, arg UpdateUserInfoParams) error {
|
func (q *Queries) UserHasRole(ctx context.Context, arg UserHasRoleParams) (int64, error) {
|
||||||
_, err := q.db.ExecContext(ctx, updateUserInfo,
|
row := q.db.QueryRowContext(ctx, userHasRole, arg.Role, arg.Subject)
|
||||||
|
var column_1 int64
|
||||||
|
err := row.Scan(&column_1)
|
||||||
|
return column_1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const addUser = `-- name: addUser :exec
|
||||||
|
INSERT INTO users (subject, password, email, email_verified, updated_at, registered, active)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`
|
||||||
|
|
||||||
|
type addUserParams struct {
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Password password.HashString `json:"password"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Registered time.Time `json:"registered"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) addUser(ctx context.Context, arg addUserParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, addUser,
|
||||||
|
arg.Subject,
|
||||||
|
arg.Password,
|
||||||
arg.Email,
|
arg.Email,
|
||||||
arg.EmailVerified,
|
arg.EmailVerified,
|
||||||
arg.Userinfo,
|
arg.UpdatedAt,
|
||||||
arg.Subject,
|
arg.Registered,
|
||||||
|
arg.Active,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateUserToken = `-- name: UpdateUserToken :exec
|
const changeUserPassword = `-- name: changeUserPassword :exec
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET access_token = ?,
|
SET password = ?,
|
||||||
refresh_token = ?,
|
updated_at=?
|
||||||
expiry = ?
|
WHERE subject = ?
|
||||||
|
AND password = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
type changeUserPasswordParams struct {
|
||||||
|
Password password.HashString `json:"password"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Password_2 password.HashString `json:"password_2"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) changeUserPassword(ctx context.Context, arg changeUserPasswordParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, changeUserPassword,
|
||||||
|
arg.Password,
|
||||||
|
arg.UpdatedAt,
|
||||||
|
arg.Subject,
|
||||||
|
arg.Password_2,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkLogin = `-- name: checkLogin :one
|
||||||
|
SELECT subject, password, EXISTS(SELECT 1 FROM otp WHERE otp.subject = users.subject) == 1 AS has_otp, email, email_verified
|
||||||
|
FROM users
|
||||||
|
WHERE users.subject = ?
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
type checkLoginRow struct {
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Password password.HashString `json:"password"`
|
||||||
|
HasOtp bool `json:"has_otp"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) checkLogin(ctx context.Context, subject string) (checkLoginRow, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, checkLogin, subject)
|
||||||
|
var i checkLoginRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.Subject,
|
||||||
|
&i.Password,
|
||||||
|
&i.HasOtp,
|
||||||
|
&i.Email,
|
||||||
|
&i.EmailVerified,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserPassword = `-- name: getUserPassword :one
|
||||||
|
SELECT password
|
||||||
|
FROM users
|
||||||
WHERE subject = ?
|
WHERE subject = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateUserTokenParams struct {
|
func (q *Queries) getUserPassword(ctx context.Context, subject string) (password.HashString, error) {
|
||||||
AccessToken sql.NullString `json:"access_token"`
|
row := q.db.QueryRowContext(ctx, getUserPassword, subject)
|
||||||
RefreshToken sql.NullString `json:"refresh_token"`
|
var password password.HashString
|
||||||
Expiry sql.NullTime `json:"expiry"`
|
err := row.Scan(&password)
|
||||||
Subject string `json:"subject"`
|
return password, err
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) UpdateUserToken(ctx context.Context, arg UpdateUserTokenParams) error {
|
|
||||||
_, err := q.db.ExecContext(ctx, updateUserToken,
|
|
||||||
arg.AccessToken,
|
|
||||||
arg.RefreshToken,
|
|
||||||
arg.Expiry,
|
|
||||||
arg.Subject,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -17,6 +17,7 @@ require (
|
|||||||
github.com/golang-migrate/migrate/v4 v4.17.1
|
github.com/golang-migrate/migrate/v4 v4.17.1
|
||||||
github.com/google/subcommands v1.2.0
|
github.com/google/subcommands v1.2.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/hardfinhq/go-date v1.20240411.1
|
||||||
github.com/julienschmidt/httprouter v1.3.0
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.22
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
github.com/mrmelon54/pronouns v1.0.3
|
github.com/mrmelon54/pronouns v1.0.3
|
||||||
|
2
go.sum
2
go.sum
@ -83,6 +83,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
|
|||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/hardfinhq/go-date v1.20240411.1 h1:UskRXxgD+4eCEa8CpiiWLiv2/Vnf1eB90Bojkf7AQ64=
|
||||||
|
github.com/hardfinhq/go-date v1.20240411.1/go.mod h1:7oxaI9XX4W3/MRDeQXec0fLXFnSJDS7BrazIY2XqPXA=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
@ -26,10 +26,9 @@ func TestManager_CheckNamespace(t *testing.T) {
|
|||||||
httpGet = func(url string) (resp *http.Response, err error) {
|
httpGet = func(url string) (resp *http.Response, err error) {
|
||||||
return &http.Response{StatusCode: http.StatusOK, Body: testBody()}, nil
|
return &http.Response{StatusCode: http.StatusOK, Body: testBody()}, nil
|
||||||
}
|
}
|
||||||
manager, err := NewManager([]SsoConfig{
|
manager, err := NewManager(map[string]SsoConfig{
|
||||||
{
|
"example.com": {
|
||||||
Addr: testAddrUrl,
|
Addr: testAddrUrl,
|
||||||
Namespace: "example.com",
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -41,10 +40,9 @@ func TestManager_FindServiceFromLogin(t *testing.T) {
|
|||||||
httpGet = func(url string) (resp *http.Response, err error) {
|
httpGet = func(url string) (resp *http.Response, err error) {
|
||||||
return &http.Response{StatusCode: http.StatusOK, Body: testBody()}, nil
|
return &http.Response{StatusCode: http.StatusOK, Body: testBody()}, nil
|
||||||
}
|
}
|
||||||
manager, err := NewManager([]SsoConfig{
|
manager, err := NewManager(map[string]SsoConfig{
|
||||||
{
|
"example.com": {
|
||||||
Addr: testAddrUrl,
|
Addr: testAddrUrl,
|
||||||
Namespace: "example.com",
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -62,6 +62,7 @@ func (s SsoConfig) FetchConfig() (*WellKnownOIDC, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WellKnownOIDC struct {
|
type WellKnownOIDC struct {
|
||||||
|
Namespace string `json:"-"`
|
||||||
Config SsoConfig `json:"-"`
|
Config SsoConfig `json:"-"`
|
||||||
Issuer string `json:"issuer"`
|
Issuer string `json:"issuer"`
|
||||||
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||||
|
@ -23,7 +23,7 @@ var ErrAuthHttpError = errors.New("auth http error")
|
|||||||
|
|
||||||
func (h *HttpServer) RequireAdminAuthentication(next UserHandler) httprouter.Handle {
|
func (h *HttpServer) RequireAdminAuthentication(next UserHandler) httprouter.Handle {
|
||||||
return h.RequireAuthentication(func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) {
|
return h.RequireAuthentication(func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, auth UserAuth) {
|
||||||
var roles string
|
var roles []string
|
||||||
if h.DbTx(rw, func(tx *database.Queries) (err error) {
|
if h.DbTx(rw, func(tx *database.Queries) (err error) {
|
||||||
roles, err = tx.GetUserRoles(req.Context(), auth.Subject)
|
roles, err = tx.GetUserRoles(req.Context(), auth.Subject)
|
||||||
return
|
return
|
||||||
|
@ -30,9 +30,9 @@ func (h *HttpServer) Home(rw http.ResponseWriter, req *http.Request, _ httproute
|
|||||||
|
|
||||||
var isAdmin bool
|
var isAdmin bool
|
||||||
h.DbTx(rw, func(tx *database.Queries) (err error) {
|
h.DbTx(rw, func(tx *database.Queries) (err error) {
|
||||||
roles, err := tx.GetUserRoles(req.Context(), auth.Subject)
|
_, err = tx.UserHasRole(req.Context(), database.UserHasRoleParams{Role: "lavender:admin", Subject: auth.Subject})
|
||||||
isAdmin = HasRole(roles, "lavender:admin")
|
isAdmin = err == nil
|
||||||
return err
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
pages.RenderPageTemplate(rw, "index", map[string]any{
|
pages.RenderPageTemplate(rw, "index", map[string]any{
|
||||||
|
@ -30,9 +30,12 @@ func (j *JWTAccessGenerate) Token(ctx context.Context, data *oauth2.GenerateBasi
|
|||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
ps := auth.ParsePermStorage(roles)
|
ps := auth.NewPermStorage()
|
||||||
|
for _, role := range roles {
|
||||||
|
ps.Set(role)
|
||||||
|
}
|
||||||
out := auth.NewPermStorage()
|
out := auth.NewPermStorage()
|
||||||
ForEachRole(data.Client.(interface{ UsePerms() string }).UsePerms(), func(role string) {
|
ForEachRole(data.Client.(interface{ UsePerms() []string }).UsePerms(), func(role string) {
|
||||||
for _, i := range ps.Filter(strings.Split(role, " ")).Dump() {
|
for _, i := range ps.Filter(strings.Split(role, " ")).Dump() {
|
||||||
out.Set(i)
|
out.Set(i)
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,16 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
func HasRole(roles []string, test string) bool {
|
||||||
"bufio"
|
for _, role := range roles {
|
||||||
"strings"
|
if role == test {
|
||||||
)
|
|
||||||
|
|
||||||
func HasRole(roles, test string) bool {
|
|
||||||
sc := bufio.NewScanner(strings.NewReader(roles))
|
|
||||||
sc.Split(bufio.ScanWords)
|
|
||||||
for sc.Scan() {
|
|
||||||
if sc.Text() == test {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func ForEachRole(roles string, next func(role string)) {
|
func ForEachRole(roles []string, next func(role string)) {
|
||||||
sc := bufio.NewScanner(strings.NewReader(roles))
|
for _, role := range roles {
|
||||||
sc.Split(bufio.ScanWords)
|
next(role)
|
||||||
for sc.Scan() {
|
|
||||||
next(sc.Text())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
sqlc.yaml
10
sqlc.yaml
@ -10,14 +10,14 @@ sql:
|
|||||||
emit_json_tags: true
|
emit_json_tags: true
|
||||||
overrides:
|
overrides:
|
||||||
- column: "users.password"
|
- column: "users.password"
|
||||||
go_type: "github.com/1f349/tulip/password.HashString"
|
go_type: "github.com/1f349/lavender/password.HashString"
|
||||||
- column: "users.birthdate"
|
- column: "users.birthdate"
|
||||||
go_type: "github.com/hardfinhq/go-date.NullDate"
|
go_type: "github.com/hardfinhq/go-date.NullDate"
|
||||||
- column: "users.role"
|
- column: "users.role"
|
||||||
go_type: "github.com/1f349/tulip/database/types.UserRole"
|
go_type: "github.com/1f349/lavender/database/types.UserRole"
|
||||||
- column: "users.pronouns"
|
- column: "users.pronouns"
|
||||||
go_type: "github.com/1f349/tulip/database/types.UserPronoun"
|
go_type: "github.com/1f349/lavender/database/types.UserPronoun"
|
||||||
- column: "users.zoneinfo"
|
- column: "users.zoneinfo"
|
||||||
go_type: "github.com/1f349/tulip/database/types.UserZone"
|
go_type: "github.com/1f349/lavender/database/types.UserZone"
|
||||||
- column: "users.locale"
|
- column: "users.locale"
|
||||||
go_type: "github.com/1f349/tulip/database/types.UserLocale"
|
go_type: "github.com/1f349/lavender/database/types.UserLocale"
|
||||||
|
Loading…
Reference in New Issue
Block a user