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
|
||||
|
||||
import "github.com/go-oauth2/oauth2/v4"
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/go-oauth2/oauth2/v4"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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) GetDomain() string { return c.Domain }
|
||||
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
|
||||
// name
|
||||
@ -22,4 +26,12 @@ func (c *ClientStore) IsSSO() bool { return c.Sso }
|
||||
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
|
||||
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
|
||||
SELECT subject, name, domain, owner, perms, public, sso, active
|
||||
SELECT subject,
|
||||
name,
|
||||
domain,
|
||||
owner_subject,
|
||||
perms,
|
||||
public,
|
||||
sso,
|
||||
active
|
||||
FROM client_store
|
||||
WHERE owner = ?
|
||||
WHERE owner_subject = ?
|
||||
OR ? = 1
|
||||
LIMIT 25 OFFSET ?
|
||||
`
|
||||
|
||||
type GetAppListParams struct {
|
||||
Owner string `json:"owner"`
|
||||
Column2 interface{} `json:"column_2"`
|
||||
Offset int64 `json:"offset"`
|
||||
OwnerSubject string `json:"owner_subject"`
|
||||
Column2 interface{} `json:"column_2"`
|
||||
Offset int64 `json:"offset"`
|
||||
}
|
||||
|
||||
type GetAppListRow struct {
|
||||
Subject string `json:"subject"`
|
||||
Name string `json:"name"`
|
||||
Domain string `json:"domain"`
|
||||
Owner string `json:"owner"`
|
||||
Perms string `json:"perms"`
|
||||
Public bool `json:"public"`
|
||||
Sso bool `json:"sso"`
|
||||
Active bool `json:"active"`
|
||||
Subject string `json:"subject"`
|
||||
Name string `json:"name"`
|
||||
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"`
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -47,7 +54,7 @@ func (q *Queries) GetAppList(ctx context.Context, arg GetAppListParams) ([]GetAp
|
||||
&i.Subject,
|
||||
&i.Name,
|
||||
&i.Domain,
|
||||
&i.Owner,
|
||||
&i.OwnerSubject,
|
||||
&i.Perms,
|
||||
&i.Public,
|
||||
&i.Sso,
|
||||
@ -67,7 +74,7 @@ func (q *Queries) GetAppList(ctx context.Context, arg GetAppListParams) ([]GetAp
|
||||
}
|
||||
|
||||
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
|
||||
WHERE subject = ?
|
||||
LIMIT 1
|
||||
@ -81,7 +88,7 @@ func (q *Queries) GetClientInfo(ctx context.Context, subject string) (ClientStor
|
||||
&i.Name,
|
||||
&i.Secret,
|
||||
&i.Domain,
|
||||
&i.Owner,
|
||||
&i.OwnerSubject,
|
||||
&i.Perms,
|
||||
&i.Public,
|
||||
&i.Sso,
|
||||
@ -91,20 +98,20 @@ func (q *Queries) GetClientInfo(ctx context.Context, subject string) (ClientStor
|
||||
}
|
||||
|
||||
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 (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`
|
||||
|
||||
type InsertClientAppParams struct {
|
||||
Subject string `json:"subject"`
|
||||
Name string `json:"name"`
|
||||
Secret string `json:"secret"`
|
||||
Domain string `json:"domain"`
|
||||
Owner string `json:"owner"`
|
||||
Perms string `json:"perms"`
|
||||
Public bool `json:"public"`
|
||||
Sso bool `json:"sso"`
|
||||
Active bool `json:"active"`
|
||||
Subject string `json:"subject"`
|
||||
Name string `json:"name"`
|
||||
Secret string `json:"secret"`
|
||||
Domain string `json:"domain"`
|
||||
Perms string `json:"perms"`
|
||||
Public bool `json:"public"`
|
||||
Sso bool `json:"sso"`
|
||||
Active bool `json:"active"`
|
||||
OwnerSubject string `json:"owner_subject"`
|
||||
}
|
||||
|
||||
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.Secret,
|
||||
arg.Domain,
|
||||
arg.Owner,
|
||||
arg.Perms,
|
||||
arg.Public,
|
||||
arg.Sso,
|
||||
arg.Active,
|
||||
arg.OwnerSubject,
|
||||
)
|
||||
return err
|
||||
}
|
||||
@ -126,17 +133,17 @@ const resetClientAppSecret = `-- name: ResetClientAppSecret :exec
|
||||
UPDATE client_store
|
||||
SET secret = ?
|
||||
WHERE subject = ?
|
||||
AND owner = ?
|
||||
AND owner_subject = ?
|
||||
`
|
||||
|
||||
type ResetClientAppSecretParams struct {
|
||||
Secret string `json:"secret"`
|
||||
Subject string `json:"subject"`
|
||||
Owner string `json:"owner"`
|
||||
Secret string `json:"secret"`
|
||||
Subject string `json:"subject"`
|
||||
OwnerSubject string `json:"owner_subject"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -149,19 +156,19 @@ SET name = ?,
|
||||
sso = ?,
|
||||
active = ?
|
||||
WHERE subject = ?
|
||||
AND owner = ?
|
||||
AND owner_subject = ?
|
||||
`
|
||||
|
||||
type UpdateClientAppParams struct {
|
||||
Name string `json:"name"`
|
||||
Domain string `json:"domain"`
|
||||
Column3 bool `json:"column_3"`
|
||||
Perms string `json:"perms"`
|
||||
Public bool `json:"public"`
|
||||
Sso bool `json:"sso"`
|
||||
Active bool `json:"active"`
|
||||
Subject string `json:"subject"`
|
||||
Owner string `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
Domain string `json:"domain"`
|
||||
Column3 bool `json:"column_3"`
|
||||
Perms string `json:"perms"`
|
||||
Public bool `json:"public"`
|
||||
Sso bool `json:"sso"`
|
||||
Active bool `json:"active"`
|
||||
Subject string `json:"subject"`
|
||||
OwnerSubject string `json:"owner_subject"`
|
||||
}
|
||||
|
||||
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.Active,
|
||||
arg.Subject,
|
||||
arg.Owner,
|
||||
arg.OwnerSubject,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
@ -7,27 +7,51 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"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
|
||||
SELECT subject,
|
||||
SELECT users.subject,
|
||||
name,
|
||||
picture,
|
||||
website,
|
||||
email,
|
||||
email_verified,
|
||||
roles,
|
||||
updated_at,
|
||||
users.updated_at as user_updated_at,
|
||||
p.updated_at as profile_updated_at,
|
||||
active
|
||||
FROM users
|
||||
LIMIT 25 OFFSET ?
|
||||
INNER JOIN main.profiles p on users.subject = p.subject
|
||||
LIMIT 50 OFFSET ?
|
||||
`
|
||||
|
||||
type GetUserListRow struct {
|
||||
Subject string `json:"subject"`
|
||||
Email string `json:"email"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
Roles string `json:"roles"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Active bool `json:"active"`
|
||||
Subject string `json:"subject"`
|
||||
Name string `json:"name"`
|
||||
Picture string `json:"picture"`
|
||||
Website string `json:"website"`
|
||||
Email string `json:"email"`
|
||||
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) {
|
||||
@ -41,10 +65,13 @@ func (q *Queries) GetUserList(ctx context.Context, offset int64) ([]GetUserListR
|
||||
var i GetUserListRow
|
||||
if err := rows.Scan(
|
||||
&i.Subject,
|
||||
&i.Name,
|
||||
&i.Picture,
|
||||
&i.Website,
|
||||
&i.Email,
|
||||
&i.EmailVerified,
|
||||
&i.Roles,
|
||||
&i.UpdatedAt,
|
||||
&i.UserUpdatedAt,
|
||||
&i.ProfileUpdatedAt,
|
||||
&i.Active,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@ -60,22 +87,50 @@ func (q *Queries) GetUserList(ctx context.Context, offset int64) ([]GetUserListR
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateUser = `-- name: UpdateUser :exec
|
||||
UPDATE users
|
||||
SET active = ?,
|
||||
roles=?
|
||||
WHERE subject = ?
|
||||
const getUsersRoles = `-- 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 /*SLICE:user_ids*/?
|
||||
`
|
||||
|
||||
type UpdateUserParams struct {
|
||||
Active bool `json:"active"`
|
||||
Roles string `json:"roles"`
|
||||
Subject string `json:"subject"`
|
||||
type GetUsersRolesRow struct {
|
||||
Role string `json:"role"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateUser, arg.Active, arg.Roles, arg.Subject)
|
||||
return err
|
||||
func (q *Queries) GetUsersRoles(ctx context.Context, userIds []int64) ([]GetUsersRolesRow, error) {
|
||||
query := getUsersRoles
|
||||
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
|
||||
|
@ -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
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/1f349/lavender/password"
|
||||
)
|
||||
|
||||
type ClientStore struct {
|
||||
Subject string `json:"subject"`
|
||||
Name string `json:"name"`
|
||||
Subject string `json:"subject"`
|
||||
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"`
|
||||
Domain string `json:"domain"`
|
||||
Owner string `json:"owner"`
|
||||
Perms string `json:"perms"`
|
||||
Public bool `json:"public"`
|
||||
Sso bool `json:"sso"`
|
||||
Active bool `json:"active"`
|
||||
Digits int64 `json:"digits"`
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
Subject string `json:"subject"`
|
||||
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 {
|
||||
Subject string `json:"subject"`
|
||||
Email string `json:"email"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
Roles string `json:"roles"`
|
||||
Userinfo string `json:"userinfo"`
|
||||
AccessToken sql.NullString `json:"access_token"`
|
||||
RefreshToken sql.NullString `json:"refresh_token"`
|
||||
Expiry sql.NullTime `json:"expiry"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Active bool `json:"active"`
|
||||
ID int64 `json:"id"`
|
||||
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"`
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
-- 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
|
||||
WHERE owner = ?
|
||||
WHERE owner_subject = ?
|
||||
OR ? = 1
|
||||
LIMIT 25 OFFSET ?;
|
||||
|
||||
-- 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 (?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
|
||||
-- name: UpdateClientApp :exec
|
||||
@ -24,10 +31,10 @@ SET name = ?,
|
||||
sso = ?,
|
||||
active = ?
|
||||
WHERE subject = ?
|
||||
AND owner = ?;
|
||||
AND owner_subject = ?;
|
||||
|
||||
-- name: ResetClientAppSecret :exec
|
||||
UPDATE client_store
|
||||
SET secret = ?
|
||||
WHERE subject = ?
|
||||
AND owner = ?;
|
||||
AND owner_subject = ?;
|
||||
|
@ -1,17 +1,27 @@
|
||||
-- name: GetUserList :many
|
||||
SELECT subject,
|
||||
SELECT users.subject,
|
||||
name,
|
||||
picture,
|
||||
website,
|
||||
email,
|
||||
email_verified,
|
||||
roles,
|
||||
updated_at,
|
||||
users.updated_at as user_updated_at,
|
||||
p.updated_at as profile_updated_at,
|
||||
active
|
||||
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
|
||||
SET active = ?,
|
||||
roles=?
|
||||
SET active = cast(? as boolean)
|
||||
WHERE subject = ?;
|
||||
|
||||
-- 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
|
||||
FROM users;
|
||||
|
||||
-- name: AddUser :exec
|
||||
INSERT INTO users (subject, email, email_verified, roles, userinfo, updated_at, active)
|
||||
-- name: addUser :exec
|
||||
INSERT INTO users (subject, password, email, email_verified, updated_at, registered, active)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||
|
||||
-- name: UpdateUserInfo :exec
|
||||
UPDATE users
|
||||
SET email = ?,
|
||||
email_verified = ?,
|
||||
userinfo = ?
|
||||
WHERE subject = ?;
|
||||
|
||||
-- name: GetUserRoles :one
|
||||
SELECT roles
|
||||
-- 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 subject = ?;
|
||||
WHERE users.subject = ?
|
||||
LIMIT 1;
|
||||
|
||||
-- name: GetUser :one
|
||||
SELECT *
|
||||
@ -24,20 +18,29 @@ FROM users
|
||||
WHERE subject = ?
|
||||
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
|
||||
SET access_token = ?,
|
||||
refresh_token = ?,
|
||||
expiry = ?
|
||||
WHERE subject = ?;
|
||||
|
||||
-- name: GetUserToken :one
|
||||
SELECT access_token, refresh_token, expiry
|
||||
FROM users
|
||||
SET password = ?,
|
||||
updated_at=?
|
||||
WHERE subject = ?
|
||||
LIMIT 1;
|
||||
|
||||
-- name: GetUserEmail :one
|
||||
SELECT email
|
||||
FROM users
|
||||
WHERE subject = ?;
|
||||
AND password = ?;
|
||||
|
@ -7,40 +7,13 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"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
|
||||
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
|
||||
WHERE subject = ?
|
||||
LIMIT 1
|
||||
@ -50,64 +23,47 @@ func (q *Queries) GetUser(ctx context.Context, subject string) (User, error) {
|
||||
row := q.db.QueryRowContext(ctx, getUser, subject)
|
||||
var i User
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Subject,
|
||||
&i.Password,
|
||||
&i.Email,
|
||||
&i.EmailVerified,
|
||||
&i.Roles,
|
||||
&i.Userinfo,
|
||||
&i.AccessToken,
|
||||
&i.RefreshToken,
|
||||
&i.Expiry,
|
||||
&i.UpdatedAt,
|
||||
&i.Registered,
|
||||
&i.Active,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getUserEmail = `-- name: GetUserEmail :one
|
||||
SELECT email
|
||||
FROM users
|
||||
WHERE subject = ?
|
||||
const getUserRoles = `-- 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 = ?
|
||||
`
|
||||
|
||||
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 getUserRoles = `-- name: GetUserRoles :one
|
||||
SELECT roles
|
||||
FROM users
|
||||
WHERE subject = ?
|
||||
`
|
||||
|
||||
func (q *Queries) GetUserRoles(ctx context.Context, subject string) (string, error) {
|
||||
row := q.db.QueryRowContext(ctx, getUserRoles, subject)
|
||||
var roles string
|
||||
err := row.Scan(&roles)
|
||||
return roles, err
|
||||
}
|
||||
|
||||
const getUserToken = `-- name: GetUserToken :one
|
||||
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
|
||||
func (q *Queries) GetUserRoles(ctx context.Context, subject string) ([]string, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getUserRoles, subject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []string
|
||||
for rows.Next() {
|
||||
var role string
|
||||
if err := rows.Scan(&role); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, role)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const hasUser = `-- name: HasUser :one
|
||||
@ -122,52 +78,117 @@ func (q *Queries) HasUser(ctx context.Context) (bool, error) {
|
||||
return hasuser, err
|
||||
}
|
||||
|
||||
const updateUserInfo = `-- name: UpdateUserInfo :exec
|
||||
UPDATE users
|
||||
SET email = ?,
|
||||
email_verified = ?,
|
||||
userinfo = ?
|
||||
WHERE subject = ?
|
||||
const userHasRole = `-- 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 = ?
|
||||
`
|
||||
|
||||
type UpdateUserInfoParams struct {
|
||||
Email string `json:"email"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
Userinfo string `json:"userinfo"`
|
||||
Subject string `json:"subject"`
|
||||
type UserHasRoleParams struct {
|
||||
Role string `json:"role"`
|
||||
Subject string `json:"subject"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateUserInfo(ctx context.Context, arg UpdateUserInfoParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateUserInfo,
|
||||
func (q *Queries) UserHasRole(ctx context.Context, arg UserHasRoleParams) (int64, error) {
|
||||
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.EmailVerified,
|
||||
arg.Userinfo,
|
||||
arg.Subject,
|
||||
arg.UpdatedAt,
|
||||
arg.Registered,
|
||||
arg.Active,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateUserToken = `-- name: UpdateUserToken :exec
|
||||
const changeUserPassword = `-- name: changeUserPassword :exec
|
||||
UPDATE users
|
||||
SET access_token = ?,
|
||||
refresh_token = ?,
|
||||
expiry = ?
|
||||
SET password = ?,
|
||||
updated_at=?
|
||||
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 = ?
|
||||
`
|
||||
|
||||
type UpdateUserTokenParams struct {
|
||||
AccessToken sql.NullString `json:"access_token"`
|
||||
RefreshToken sql.NullString `json:"refresh_token"`
|
||||
Expiry sql.NullTime `json:"expiry"`
|
||||
Subject string `json:"subject"`
|
||||
}
|
||||
|
||||
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
|
||||
func (q *Queries) getUserPassword(ctx context.Context, subject string) (password.HashString, error) {
|
||||
row := q.db.QueryRowContext(ctx, getUserPassword, subject)
|
||||
var password password.HashString
|
||||
err := row.Scan(&password)
|
||||
return password, err
|
||||
}
|
||||
|
1
go.mod
1
go.mod
@ -17,6 +17,7 @@ require (
|
||||
github.com/golang-migrate/migrate/v4 v4.17.1
|
||||
github.com/google/subcommands v1.2.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/mattn/go-sqlite3 v1.14.22
|
||||
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.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
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.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
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) {
|
||||
return &http.Response{StatusCode: http.StatusOK, Body: testBody()}, nil
|
||||
}
|
||||
manager, err := NewManager([]SsoConfig{
|
||||
{
|
||||
Addr: testAddrUrl,
|
||||
Namespace: "example.com",
|
||||
manager, err := NewManager(map[string]SsoConfig{
|
||||
"example.com": {
|
||||
Addr: testAddrUrl,
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
@ -41,10 +40,9 @@ func TestManager_FindServiceFromLogin(t *testing.T) {
|
||||
httpGet = func(url string) (resp *http.Response, err error) {
|
||||
return &http.Response{StatusCode: http.StatusOK, Body: testBody()}, nil
|
||||
}
|
||||
manager, err := NewManager([]SsoConfig{
|
||||
{
|
||||
Addr: testAddrUrl,
|
||||
Namespace: "example.com",
|
||||
manager, err := NewManager(map[string]SsoConfig{
|
||||
"example.com": {
|
||||
Addr: testAddrUrl,
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
@ -62,6 +62,7 @@ func (s SsoConfig) FetchConfig() (*WellKnownOIDC, error) {
|
||||
}
|
||||
|
||||
type WellKnownOIDC struct {
|
||||
Namespace string `json:"-"`
|
||||
Config SsoConfig `json:"-"`
|
||||
Issuer string `json:"issuer"`
|
||||
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||
|
@ -23,7 +23,7 @@ var ErrAuthHttpError = errors.New("auth http error")
|
||||
|
||||
func (h *HttpServer) RequireAdminAuthentication(next UserHandler) httprouter.Handle {
|
||||
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) {
|
||||
roles, err = tx.GetUserRoles(req.Context(), auth.Subject)
|
||||
return
|
||||
|
@ -30,9 +30,9 @@ func (h *HttpServer) Home(rw http.ResponseWriter, req *http.Request, _ httproute
|
||||
|
||||
var isAdmin bool
|
||||
h.DbTx(rw, func(tx *database.Queries) (err error) {
|
||||
roles, err := tx.GetUserRoles(req.Context(), auth.Subject)
|
||||
isAdmin = HasRole(roles, "lavender:admin")
|
||||
return err
|
||||
_, err = tx.UserHasRole(req.Context(), database.UserHasRoleParams{Role: "lavender:admin", Subject: auth.Subject})
|
||||
isAdmin = err == nil
|
||||
return nil
|
||||
})
|
||||
|
||||
pages.RenderPageTemplate(rw, "index", map[string]any{
|
||||
|
@ -30,9 +30,12 @@ func (j *JWTAccessGenerate) Token(ctx context.Context, data *oauth2.GenerateBasi
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
ps := auth.ParsePermStorage(roles)
|
||||
ps := auth.NewPermStorage()
|
||||
for _, role := range roles {
|
||||
ps.Set(role)
|
||||
}
|
||||
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() {
|
||||
out.Set(i)
|
||||
}
|
||||
|
@ -1,25 +1,16 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func HasRole(roles, test string) bool {
|
||||
sc := bufio.NewScanner(strings.NewReader(roles))
|
||||
sc.Split(bufio.ScanWords)
|
||||
for sc.Scan() {
|
||||
if sc.Text() == test {
|
||||
func HasRole(roles []string, test string) bool {
|
||||
for _, role := range roles {
|
||||
if role == test {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ForEachRole(roles string, next func(role string)) {
|
||||
sc := bufio.NewScanner(strings.NewReader(roles))
|
||||
sc.Split(bufio.ScanWords)
|
||||
for sc.Scan() {
|
||||
next(sc.Text())
|
||||
func ForEachRole(roles []string, next func(role string)) {
|
||||
for _, role := range roles {
|
||||
next(role)
|
||||
}
|
||||
}
|
||||
|
10
sqlc.yaml
10
sqlc.yaml
@ -10,14 +10,14 @@ sql:
|
||||
emit_json_tags: true
|
||||
overrides:
|
||||
- column: "users.password"
|
||||
go_type: "github.com/1f349/tulip/password.HashString"
|
||||
go_type: "github.com/1f349/lavender/password.HashString"
|
||||
- column: "users.birthdate"
|
||||
go_type: "github.com/hardfinhq/go-date.NullDate"
|
||||
- column: "users.role"
|
||||
go_type: "github.com/1f349/tulip/database/types.UserRole"
|
||||
go_type: "github.com/1f349/lavender/database/types.UserRole"
|
||||
- column: "users.pronouns"
|
||||
go_type: "github.com/1f349/tulip/database/types.UserPronoun"
|
||||
go_type: "github.com/1f349/lavender/database/types.UserPronoun"
|
||||
- column: "users.zoneinfo"
|
||||
go_type: "github.com/1f349/tulip/database/types.UserZone"
|
||||
go_type: "github.com/1f349/lavender/database/types.UserZone"
|
||||
- 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