Merge pull request 'add oidc sqlite' (#1) from OIDCSqlite into main

Reviewed-on: https://git.infra.nkode.tech/dkelly/nkode-core/pulls/1
This commit is contained in:
dkelly
2025-02-13 10:57:07 +00:00
7 changed files with 490 additions and 6 deletions

View File

@@ -1,6 +1,14 @@
version: "3"
vars:
test_db: "~/databases/test.db"
schema_db: "./sqlite/schema.sql"
tasks:
sql:
sqlc:
cmds:
- sqlc generate
rebuild_test_db:
cmds:
- rm {{.test_db}}
- sqlite3 {{.test_db}} < {{.schema_db}}

View File

@@ -5,6 +5,7 @@ import (
"git.infra.nkode.tech/dkelly/nkode-core/config"
"git.infra.nkode.tech/dkelly/nkode-core/email"
"git.infra.nkode.tech/dkelly/nkode-core/entities"
"git.infra.nkode.tech/dkelly/nkode-core/memcache"
"git.infra.nkode.tech/dkelly/nkode-core/repository"
"git.infra.nkode.tech/dkelly/nkode-core/security"
"github.com/google/uuid"
@@ -23,7 +24,7 @@ type NKodeAPI struct {
repo repository.CustomerUserRepository
signupSessionCache *cache.Cache
emailQueue *email.Queue
forgotNkodeCache mem_cache.ForgotNKodeCache
forgotNkodeCache memcache.ForgotNKodeCache
}
func NewNKodeAPI(repo repository.CustomerUserRepository, queue *email.Queue) NKodeAPI {
@@ -31,7 +32,7 @@ func NewNKodeAPI(repo repository.CustomerUserRepository, queue *email.Queue) NKo
repo: repo,
emailQueue: queue,
signupSessionCache: cache.New(sessionExpiration, sessionCleanupInterval),
forgotNkodeCache: mem_cache.NewForgotNKodeCache(),
forgotNkodeCache: memcache.NewForgotNKodeCache(),
}
}

View File

@@ -1,4 +1,4 @@
package mem_cache
package memcache
import (
"git.infra.nkode.tech/dkelly/nkode-core/entities"

View File

@@ -6,8 +6,43 @@ package sqlc
import (
"database/sql"
"time"
)
type AuthorizationCode struct {
ID int64
Code string
CodeChallenge string
CodeChallengeMethod string
UserID string
ClientID string
Scope sql.NullString
RedirectUri string
CreatedAt sql.NullTime
ExpiresAt time.Time
UsedAt sql.NullTime
}
type Client struct {
ID string
Name string
Owner string
CreatedAt sql.NullTime
}
type ClientApproval struct {
ID int64
UserID string
ClientID string
}
type ClientRedirect struct {
ID int64
Uri string
ClientID string
CreatedAt sql.NullTime
}
type Customer struct {
ID string
MaxNkodeLen int64
@@ -27,6 +62,17 @@ type SvgIcon struct {
Svg string
}
type Token struct {
ID int64
TokenType string
TokenValue string
UserID string
ClientID string
Scope sql.NullString
CreatedAt sql.NullTime
ExpiresAt time.Time
}
type User struct {
ID string
Email string

View File

@@ -8,6 +8,7 @@ package sqlc
import (
"context"
"database/sql"
"time"
)
const addSvg = `-- name: AddSvg :exec
@@ -33,6 +34,69 @@ func (q *Queries) AddUserPermission(ctx context.Context, arg AddUserPermissionPa
return err
}
const approveClient = `-- name: ApproveClient :exec
INSERT INTO client_approvals (user_id, client_id)
VALUES (?, ?)
`
type ApproveClientParams struct {
UserID string
ClientID string
}
func (q *Queries) ApproveClient(ctx context.Context, arg ApproveClientParams) error {
_, err := q.db.ExecContext(ctx, approveClient, arg.UserID, arg.ClientID)
return err
}
const clientApproved = `-- name: ClientApproved :one
SELECT id, user_id, client_id
FROM client_approvals
WHERE user_id = ? AND client_id = ?
`
type ClientApprovedParams struct {
UserID string
ClientID string
}
func (q *Queries) ClientApproved(ctx context.Context, arg ClientApprovedParams) (ClientApproval, error) {
row := q.db.QueryRowContext(ctx, clientApproved, arg.UserID, arg.ClientID)
var i ClientApproval
err := row.Scan(&i.ID, &i.UserID, &i.ClientID)
return i, err
}
const createAuthorizationCode = `-- name: CreateAuthorizationCode :exec
INSERT INTO authorization_codes (code, code_challenge, code_challenge_method, user_id, client_id, scope, redirect_uri, expires_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`
type CreateAuthorizationCodeParams struct {
Code string
CodeChallenge string
CodeChallengeMethod string
UserID string
ClientID string
Scope sql.NullString
RedirectUri string
ExpiresAt time.Time
}
func (q *Queries) CreateAuthorizationCode(ctx context.Context, arg CreateAuthorizationCodeParams) error {
_, err := q.db.ExecContext(ctx, createAuthorizationCode,
arg.Code,
arg.CodeChallenge,
arg.CodeChallengeMethod,
arg.UserID,
arg.ClientID,
arg.Scope,
arg.RedirectUri,
arg.ExpiresAt,
)
return err
}
const createCustomer = `-- name: CreateCustomer :exec
INSERT INTO customer (
id
@@ -81,6 +145,63 @@ func (q *Queries) CreateCustomer(ctx context.Context, arg CreateCustomerParams)
return err
}
const createOIDCClient = `-- name: CreateOIDCClient :exec
INSERT INTO clients (id, name, owner)
VALUES (?, ?, ?)
`
type CreateOIDCClientParams struct {
ID string
Name string
Owner string
}
func (q *Queries) CreateOIDCClient(ctx context.Context, arg CreateOIDCClientParams) error {
_, err := q.db.ExecContext(ctx, createOIDCClient, arg.ID, arg.Name, arg.Owner)
return err
}
const createRedirectURI = `-- name: CreateRedirectURI :exec
INSERT INTO client_redirects (uri, client_id)
VALUES (?, ?)
`
type CreateRedirectURIParams struct {
Uri string
ClientID string
}
func (q *Queries) CreateRedirectURI(ctx context.Context, arg CreateRedirectURIParams) error {
_, err := q.db.ExecContext(ctx, createRedirectURI, arg.Uri, arg.ClientID)
return err
}
const createToken = `-- name: CreateToken :exec
INSERT INTO tokens (token_type, token_value, user_id, client_id, scope, expires_at)
VALUES (?, ?, ?, ?, ?, ?)
`
type CreateTokenParams struct {
TokenType string
TokenValue string
UserID string
ClientID string
Scope sql.NullString
ExpiresAt time.Time
}
func (q *Queries) CreateToken(ctx context.Context, arg CreateTokenParams) error {
_, err := q.db.ExecContext(ctx, createToken,
arg.TokenType,
arg.TokenValue,
arg.UserID,
arg.ClientID,
arg.Scope,
arg.ExpiresAt,
)
return err
}
const createUser = `-- name: CreateUser :exec
INSERT INTO user (
id
@@ -150,6 +271,110 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) error {
return err
}
const deleteAuthCode = `-- name: DeleteAuthCode :exec
DELETE FROM authorization_codes
WHERE code = ?
`
func (q *Queries) DeleteAuthCode(ctx context.Context, code string) error {
_, err := q.db.ExecContext(ctx, deleteAuthCode, code)
return err
}
const deleteOldAuthCodes = `-- name: DeleteOldAuthCodes :exec
DELETE FROM authorization_codes
WHERE expires_at < CURRENT_TIMESTAMP
`
func (q *Queries) DeleteOldAuthCodes(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteOldAuthCodes)
return err
}
const deleteOldTokens = `-- name: DeleteOldTokens :exec
DELETE FROM tokens
WHERE expires_at < CURRENT_TIMESTAMP
`
func (q *Queries) DeleteOldTokens(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteOldTokens)
return err
}
const deleteRedirectURI = `-- name: DeleteRedirectURI :exec
DELETE FROM client_redirects
WHERE uri = ? AND client_id = ?
`
type DeleteRedirectURIParams struct {
Uri string
ClientID string
}
func (q *Queries) DeleteRedirectURI(ctx context.Context, arg DeleteRedirectURIParams) error {
_, err := q.db.ExecContext(ctx, deleteRedirectURI, arg.Uri, arg.ClientID)
return err
}
const getAuthorizationCode = `-- name: GetAuthorizationCode :one
SELECT id, code, code_challenge, code_challenge_method, user_id, client_id, scope, redirect_uri, created_at, expires_at, used_at
FROM authorization_codes
WHERE code = ?
`
func (q *Queries) GetAuthorizationCode(ctx context.Context, code string) (AuthorizationCode, error) {
row := q.db.QueryRowContext(ctx, getAuthorizationCode, code)
var i AuthorizationCode
err := row.Scan(
&i.ID,
&i.Code,
&i.CodeChallenge,
&i.CodeChallengeMethod,
&i.UserID,
&i.ClientID,
&i.Scope,
&i.RedirectUri,
&i.CreatedAt,
&i.ExpiresAt,
&i.UsedAt,
)
return i, err
}
const getClientRedirectURIs = `-- name: GetClientRedirectURIs :many
SELECT id, uri, client_id, created_at
FROM client_redirects
WHERE client_id = ?
`
func (q *Queries) GetClientRedirectURIs(ctx context.Context, clientID string) ([]ClientRedirect, error) {
rows, err := q.db.QueryContext(ctx, getClientRedirectURIs, clientID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ClientRedirect
for rows.Next() {
var i ClientRedirect
if err := rows.Scan(
&i.ID,
&i.Uri,
&i.ClientID,
&i.CreatedAt,
); 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 getCustomer = `-- name: GetCustomer :one
SELECT
max_nkode_len
@@ -191,6 +416,24 @@ func (q *Queries) GetCustomer(ctx context.Context, id string) (GetCustomerRow, e
return i, err
}
const getOIDCClientByID = `-- name: GetOIDCClientByID :one
SELECT id, name, owner, created_at
FROM clients
WHERE id = ?
`
func (q *Queries) GetOIDCClientByID(ctx context.Context, id string) (Client, error) {
row := q.db.QueryRowContext(ctx, getOIDCClientByID, id)
var i Client
err := row.Scan(
&i.ID,
&i.Name,
&i.Owner,
&i.CreatedAt,
)
return i, err
}
const getSvgCount = `-- name: GetSvgCount :one
SELECT COUNT(*) as count FROM svg_icon
`
@@ -215,6 +458,28 @@ func (q *Queries) GetSvgId(ctx context.Context, id int64) (string, error) {
return svg, err
}
const getTokenByValue = `-- name: GetTokenByValue :one
SELECT id, token_type, token_value, user_id, client_id, scope, created_at, expires_at
FROM tokens
WHERE token_value = ?
`
func (q *Queries) GetTokenByValue(ctx context.Context, tokenValue string) (Token, error) {
row := q.db.QueryRowContext(ctx, getTokenByValue, tokenValue)
var i Token
err := row.Scan(
&i.ID,
&i.TokenType,
&i.TokenValue,
&i.UserID,
&i.ClientID,
&i.Scope,
&i.CreatedAt,
&i.ExpiresAt,
)
return i, err
}
const getUser = `-- name: GetUser :one
SELECT
id
@@ -282,6 +547,42 @@ func (q *Queries) GetUser(ctx context.Context, arg GetUserParams) (GetUserRow, e
return i, err
}
const getUserClients = `-- name: GetUserClients :many
SELECT id, name, owner, created_at
FROM clients
WHERE owner = ?
`
// -------- go-oidc
func (q *Queries) GetUserClients(ctx context.Context, owner string) ([]Client, error) {
rows, err := q.db.QueryContext(ctx, getUserClients, owner)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Client
for rows.Next() {
var i Client
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Owner,
&i.CreatedAt,
); 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 getUserPermissions = `-- name: GetUserPermissions :many
SELECT permission FROM user_permission WHERE user_id = ?
`

View File

@@ -143,3 +143,72 @@ SELECT permission FROM user_permission WHERE user_id = ?;
-- name: AddUserPermission :exec
INSERT INTO user_permission (user_id, permission) VALUES (?, ?);
---------- go-oidc
-- name: GetUserClients :many
SELECT *
FROM clients
WHERE owner = ?;
-- name: GetOIDCClientByID :one
SELECT *
FROM clients
WHERE id = ?;
-- name: CreateOIDCClient :exec
INSERT INTO clients (id, name, owner)
VALUES (?, ?, ?);
-- name: CreateRedirectURI :exec
INSERT INTO client_redirects (uri, client_id)
VALUES (?, ?);
-- name: DeleteRedirectURI :exec
DELETE FROM client_redirects
WHERE uri = ? AND client_id = ?;
-- name: GetClientRedirectURIs :many
SELECT *
FROM client_redirects
WHERE client_id = ?;
-- name: GetAuthorizationCode :one
SELECT *
FROM authorization_codes
WHERE code = ?;
-- name: CreateAuthorizationCode :exec
INSERT INTO authorization_codes (code, code_challenge, code_challenge_method, user_id, client_id, scope, redirect_uri, expires_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?);
-- name: DeleteOldAuthCodes :exec
DELETE FROM authorization_codes
WHERE expires_at < CURRENT_TIMESTAMP;
-- name: DeleteOldTokens :exec
DELETE FROM tokens
WHERE expires_at < CURRENT_TIMESTAMP;
-- name: GetTokenByValue :one
SELECT *
FROM tokens
WHERE token_value = ?;
-- name: CreateToken :exec
INSERT INTO tokens (token_type, token_value, user_id, client_id, scope, expires_at)
VALUES (?, ?, ?, ?, ?, ?);
-- name: ApproveClient :exec
INSERT INTO client_approvals (user_id, client_id)
VALUES (?, ?);
-- name: ClientApproved :one
SELECT *
FROM client_approvals
WHERE user_id = ? AND client_id = ?;
-- name: DeleteAuthCode :exec
DELETE FROM authorization_codes
WHERE code = ?;

View File

@@ -64,3 +64,62 @@ CREATE TABLE IF NOT EXISTS user_permission (
,FOREIGN KEY (user_id) REFERENCES user(id)
,UNIQUE(user_id, permission)
);
---- go-oidc
CREATE TABLE IF NOT EXISTS clients (
id TEXT PRIMARY KEY
,name TEXT NOT NULL
,owner TEXT NOT NULL
,created_at DATETIME DEFAULT CURRENT_TIMESTAMP
,FOREIGN KEY (owner) REFERENCES user (id)
,UNIQUE(name, owner)
);
CREATE TABLE IF NOT EXISTS client_redirects (
id INTEGER PRIMARY KEY AUTOINCREMENT
,uri TEXT NOT NULL
,client_id TEXT NOT NULL
,created_at DATETIME DEFAULT CURRENT_TIMESTAMP
,FOREIGN KEY (client_id) REFERENCES clients (id)
,UNIQUE(uri, client_id)
);
CREATE TABLE IF NOT EXISTS authorization_codes (
id INTEGER PRIMARY KEY AUTOINCREMENT
,code TEXT NOT NULL UNIQUE
,code_challenge TEXT NOT NULL UNIQUE
,code_challenge_method TEXT NOT NULL CHECK (code_challenge_method IN ('S256', 'plain'))
,user_id TEXT NOT NULL
,client_id TEXT NOT NULL
,scope TEXT
,redirect_uri TEXT NOT NULL
,created_at DATETIME DEFAULT CURRENT_TIMESTAMP
,expires_at DATETIME NOT NULL
,used_at DATETIME
,FOREIGN KEY (user_id) REFERENCES user (id)
,FOREIGN KEY (client_id) REFERENCES client (id)
);
CREATE TABLE IF NOT EXISTS tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT
,token_type TEXT NOT NULL CHECK (token_type IN ('access', 'refresh'))
,token_value TEXT NOT NULL UNIQUE
,user_id TEXT NOT NULL
,client_id TEXT NOT NULL
,scope TEXT
,created_at DATETIME DEFAULT CURRENT_TIMESTAMP
,expires_at DATETIME NOT NULL
,FOREIGN KEY (user_id) REFERENCES user (id)
,FOREIGN KEY (client_id) REFERENCES clients (id)
);
CREATE TABLE IF NOT EXISTS client_approvals (
id INTEGER PRIMARY KEY AUTOINCREMENT
,user_id TEXT NOT NULL
,client_id TEXT NOT NULL
,UNIQUE(user_id, client_id)
,FOREIGN KEY (user_id) REFERENCES users (id)
,FOREIGN KEY (client_id) REFERENCES clients (id)
);