mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-02-23 01:11:59 +00:00
feat: oidc (#605)
* chore: add oidc base config * wip: authorize page * feat: implement basic oidc functionality * refactor: implement oidc following tinyauth patterns * feat: adapt frontend to oidc flow * fix: review comments * fix: oidc review comments * feat: refresh token grant type support * feat: cleanup expired oidc sessions * feat: frontend i18n * fix: fix typo in error screen * tests: add basic testing * fix: more review comments * refactor: rework oidc error messages * feat: openid discovery endpoint * feat: jwk endpoint * i18n: fix typo * fix: more rabbit nitpicks * fix: final review comments * i18n: authorize page error messages
This commit is contained in:
@@ -4,6 +4,34 @@
|
||||
|
||||
package repository
|
||||
|
||||
type OidcCode struct {
|
||||
Sub string
|
||||
CodeHash string
|
||||
Scope string
|
||||
RedirectURI string
|
||||
ClientID string
|
||||
ExpiresAt int64
|
||||
}
|
||||
|
||||
type OidcToken struct {
|
||||
Sub string
|
||||
AccessTokenHash string
|
||||
RefreshTokenHash string
|
||||
Scope string
|
||||
ClientID string
|
||||
TokenExpiresAt int64
|
||||
RefreshTokenExpiresAt int64
|
||||
}
|
||||
|
||||
type OidcUserinfo struct {
|
||||
Sub string
|
||||
Name string
|
||||
PreferredUsername string
|
||||
Email string
|
||||
Groups string
|
||||
UpdatedAt int64
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
UUID string
|
||||
Username string
|
||||
|
||||
470
internal/repository/oidc_queries.sql.go
Normal file
470
internal/repository/oidc_queries.sql.go
Normal file
@@ -0,0 +1,470 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// source: oidc_queries.sql
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const createOidcCode = `-- name: CreateOidcCode :one
|
||||
INSERT INTO "oidc_codes" (
|
||||
"sub",
|
||||
"code_hash",
|
||||
"scope",
|
||||
"redirect_uri",
|
||||
"client_id",
|
||||
"expires_at"
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at
|
||||
`
|
||||
|
||||
type CreateOidcCodeParams struct {
|
||||
Sub string
|
||||
CodeHash string
|
||||
Scope string
|
||||
RedirectURI string
|
||||
ClientID string
|
||||
ExpiresAt int64
|
||||
}
|
||||
|
||||
func (q *Queries) CreateOidcCode(ctx context.Context, arg CreateOidcCodeParams) (OidcCode, error) {
|
||||
row := q.db.QueryRowContext(ctx, createOidcCode,
|
||||
arg.Sub,
|
||||
arg.CodeHash,
|
||||
arg.Scope,
|
||||
arg.RedirectURI,
|
||||
arg.ClientID,
|
||||
arg.ExpiresAt,
|
||||
)
|
||||
var i OidcCode
|
||||
err := row.Scan(
|
||||
&i.Sub,
|
||||
&i.CodeHash,
|
||||
&i.Scope,
|
||||
&i.RedirectURI,
|
||||
&i.ClientID,
|
||||
&i.ExpiresAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const createOidcToken = `-- name: CreateOidcToken :one
|
||||
INSERT INTO "oidc_tokens" (
|
||||
"sub",
|
||||
"access_token_hash",
|
||||
"refresh_token_hash",
|
||||
"scope",
|
||||
"client_id",
|
||||
"token_expires_at",
|
||||
"refresh_token_expires_at"
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at
|
||||
`
|
||||
|
||||
type CreateOidcTokenParams struct {
|
||||
Sub string
|
||||
AccessTokenHash string
|
||||
RefreshTokenHash string
|
||||
Scope string
|
||||
ClientID string
|
||||
TokenExpiresAt int64
|
||||
RefreshTokenExpiresAt int64
|
||||
}
|
||||
|
||||
func (q *Queries) CreateOidcToken(ctx context.Context, arg CreateOidcTokenParams) (OidcToken, error) {
|
||||
row := q.db.QueryRowContext(ctx, createOidcToken,
|
||||
arg.Sub,
|
||||
arg.AccessTokenHash,
|
||||
arg.RefreshTokenHash,
|
||||
arg.Scope,
|
||||
arg.ClientID,
|
||||
arg.TokenExpiresAt,
|
||||
arg.RefreshTokenExpiresAt,
|
||||
)
|
||||
var i OidcToken
|
||||
err := row.Scan(
|
||||
&i.Sub,
|
||||
&i.AccessTokenHash,
|
||||
&i.RefreshTokenHash,
|
||||
&i.Scope,
|
||||
&i.ClientID,
|
||||
&i.TokenExpiresAt,
|
||||
&i.RefreshTokenExpiresAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const createOidcUserInfo = `-- name: CreateOidcUserInfo :one
|
||||
INSERT INTO "oidc_userinfo" (
|
||||
"sub",
|
||||
"name",
|
||||
"preferred_username",
|
||||
"email",
|
||||
"groups",
|
||||
"updated_at"
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING sub, name, preferred_username, email, "groups", updated_at
|
||||
`
|
||||
|
||||
type CreateOidcUserInfoParams struct {
|
||||
Sub string
|
||||
Name string
|
||||
PreferredUsername string
|
||||
Email string
|
||||
Groups string
|
||||
UpdatedAt int64
|
||||
}
|
||||
|
||||
func (q *Queries) CreateOidcUserInfo(ctx context.Context, arg CreateOidcUserInfoParams) (OidcUserinfo, error) {
|
||||
row := q.db.QueryRowContext(ctx, createOidcUserInfo,
|
||||
arg.Sub,
|
||||
arg.Name,
|
||||
arg.PreferredUsername,
|
||||
arg.Email,
|
||||
arg.Groups,
|
||||
arg.UpdatedAt,
|
||||
)
|
||||
var i OidcUserinfo
|
||||
err := row.Scan(
|
||||
&i.Sub,
|
||||
&i.Name,
|
||||
&i.PreferredUsername,
|
||||
&i.Email,
|
||||
&i.Groups,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const deleteExpiredOidcCodes = `-- name: DeleteExpiredOidcCodes :many
|
||||
DELETE FROM "oidc_codes"
|
||||
WHERE "expires_at" < ?
|
||||
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteExpiredOidcCodes(ctx context.Context, expiresAt int64) ([]OidcCode, error) {
|
||||
rows, err := q.db.QueryContext(ctx, deleteExpiredOidcCodes, expiresAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []OidcCode
|
||||
for rows.Next() {
|
||||
var i OidcCode
|
||||
if err := rows.Scan(
|
||||
&i.Sub,
|
||||
&i.CodeHash,
|
||||
&i.Scope,
|
||||
&i.RedirectURI,
|
||||
&i.ClientID,
|
||||
&i.ExpiresAt,
|
||||
); 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 deleteExpiredOidcTokens = `-- name: DeleteExpiredOidcTokens :many
|
||||
DELETE FROM "oidc_tokens"
|
||||
WHERE "token_expires_at" < ? AND "refresh_token_expires_at" < ?
|
||||
RETURNING sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at
|
||||
`
|
||||
|
||||
type DeleteExpiredOidcTokensParams struct {
|
||||
TokenExpiresAt int64
|
||||
RefreshTokenExpiresAt int64
|
||||
}
|
||||
|
||||
func (q *Queries) DeleteExpiredOidcTokens(ctx context.Context, arg DeleteExpiredOidcTokensParams) ([]OidcToken, error) {
|
||||
rows, err := q.db.QueryContext(ctx, deleteExpiredOidcTokens, arg.TokenExpiresAt, arg.RefreshTokenExpiresAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []OidcToken
|
||||
for rows.Next() {
|
||||
var i OidcToken
|
||||
if err := rows.Scan(
|
||||
&i.Sub,
|
||||
&i.AccessTokenHash,
|
||||
&i.RefreshTokenHash,
|
||||
&i.Scope,
|
||||
&i.ClientID,
|
||||
&i.TokenExpiresAt,
|
||||
&i.RefreshTokenExpiresAt,
|
||||
); 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 deleteOidcCode = `-- name: DeleteOidcCode :exec
|
||||
DELETE FROM "oidc_codes"
|
||||
WHERE "code_hash" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteOidcCode(ctx context.Context, codeHash string) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteOidcCode, codeHash)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteOidcCodeBySub = `-- name: DeleteOidcCodeBySub :exec
|
||||
DELETE FROM "oidc_codes"
|
||||
WHERE "sub" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteOidcCodeBySub(ctx context.Context, sub string) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteOidcCodeBySub, sub)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteOidcToken = `-- name: DeleteOidcToken :exec
|
||||
DELETE FROM "oidc_tokens"
|
||||
WHERE "access_token_hash" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteOidcToken(ctx context.Context, accessTokenHash string) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteOidcToken, accessTokenHash)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteOidcTokenBySub = `-- name: DeleteOidcTokenBySub :exec
|
||||
DELETE FROM "oidc_tokens"
|
||||
WHERE "sub" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteOidcTokenBySub(ctx context.Context, sub string) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteOidcTokenBySub, sub)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteOidcUserInfo = `-- name: DeleteOidcUserInfo :exec
|
||||
DELETE FROM "oidc_userinfo"
|
||||
WHERE "sub" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteOidcUserInfo(ctx context.Context, sub string) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteOidcUserInfo, sub)
|
||||
return err
|
||||
}
|
||||
|
||||
const getOidcCode = `-- name: GetOidcCode :one
|
||||
DELETE FROM "oidc_codes"
|
||||
WHERE "code_hash" = ?
|
||||
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at
|
||||
`
|
||||
|
||||
func (q *Queries) GetOidcCode(ctx context.Context, codeHash string) (OidcCode, error) {
|
||||
row := q.db.QueryRowContext(ctx, getOidcCode, codeHash)
|
||||
var i OidcCode
|
||||
err := row.Scan(
|
||||
&i.Sub,
|
||||
&i.CodeHash,
|
||||
&i.Scope,
|
||||
&i.RedirectURI,
|
||||
&i.ClientID,
|
||||
&i.ExpiresAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getOidcCodeBySub = `-- name: GetOidcCodeBySub :one
|
||||
DELETE FROM "oidc_codes"
|
||||
WHERE "sub" = ?
|
||||
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at
|
||||
`
|
||||
|
||||
func (q *Queries) GetOidcCodeBySub(ctx context.Context, sub string) (OidcCode, error) {
|
||||
row := q.db.QueryRowContext(ctx, getOidcCodeBySub, sub)
|
||||
var i OidcCode
|
||||
err := row.Scan(
|
||||
&i.Sub,
|
||||
&i.CodeHash,
|
||||
&i.Scope,
|
||||
&i.RedirectURI,
|
||||
&i.ClientID,
|
||||
&i.ExpiresAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getOidcCodeBySubUnsafe = `-- name: GetOidcCodeBySubUnsafe :one
|
||||
SELECT sub, code_hash, scope, redirect_uri, client_id, expires_at FROM "oidc_codes"
|
||||
WHERE "sub" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) GetOidcCodeBySubUnsafe(ctx context.Context, sub string) (OidcCode, error) {
|
||||
row := q.db.QueryRowContext(ctx, getOidcCodeBySubUnsafe, sub)
|
||||
var i OidcCode
|
||||
err := row.Scan(
|
||||
&i.Sub,
|
||||
&i.CodeHash,
|
||||
&i.Scope,
|
||||
&i.RedirectURI,
|
||||
&i.ClientID,
|
||||
&i.ExpiresAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getOidcCodeUnsafe = `-- name: GetOidcCodeUnsafe :one
|
||||
SELECT sub, code_hash, scope, redirect_uri, client_id, expires_at FROM "oidc_codes"
|
||||
WHERE "code_hash" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) GetOidcCodeUnsafe(ctx context.Context, codeHash string) (OidcCode, error) {
|
||||
row := q.db.QueryRowContext(ctx, getOidcCodeUnsafe, codeHash)
|
||||
var i OidcCode
|
||||
err := row.Scan(
|
||||
&i.Sub,
|
||||
&i.CodeHash,
|
||||
&i.Scope,
|
||||
&i.RedirectURI,
|
||||
&i.ClientID,
|
||||
&i.ExpiresAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getOidcToken = `-- name: GetOidcToken :one
|
||||
SELECT sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at FROM "oidc_tokens"
|
||||
WHERE "access_token_hash" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) GetOidcToken(ctx context.Context, accessTokenHash string) (OidcToken, error) {
|
||||
row := q.db.QueryRowContext(ctx, getOidcToken, accessTokenHash)
|
||||
var i OidcToken
|
||||
err := row.Scan(
|
||||
&i.Sub,
|
||||
&i.AccessTokenHash,
|
||||
&i.RefreshTokenHash,
|
||||
&i.Scope,
|
||||
&i.ClientID,
|
||||
&i.TokenExpiresAt,
|
||||
&i.RefreshTokenExpiresAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getOidcTokenByRefreshToken = `-- name: GetOidcTokenByRefreshToken :one
|
||||
SELECT sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at FROM "oidc_tokens"
|
||||
WHERE "refresh_token_hash" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) GetOidcTokenByRefreshToken(ctx context.Context, refreshTokenHash string) (OidcToken, error) {
|
||||
row := q.db.QueryRowContext(ctx, getOidcTokenByRefreshToken, refreshTokenHash)
|
||||
var i OidcToken
|
||||
err := row.Scan(
|
||||
&i.Sub,
|
||||
&i.AccessTokenHash,
|
||||
&i.RefreshTokenHash,
|
||||
&i.Scope,
|
||||
&i.ClientID,
|
||||
&i.TokenExpiresAt,
|
||||
&i.RefreshTokenExpiresAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getOidcTokenBySub = `-- name: GetOidcTokenBySub :one
|
||||
SELECT sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at FROM "oidc_tokens"
|
||||
WHERE "sub" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) GetOidcTokenBySub(ctx context.Context, sub string) (OidcToken, error) {
|
||||
row := q.db.QueryRowContext(ctx, getOidcTokenBySub, sub)
|
||||
var i OidcToken
|
||||
err := row.Scan(
|
||||
&i.Sub,
|
||||
&i.AccessTokenHash,
|
||||
&i.RefreshTokenHash,
|
||||
&i.Scope,
|
||||
&i.ClientID,
|
||||
&i.TokenExpiresAt,
|
||||
&i.RefreshTokenExpiresAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getOidcUserInfo = `-- name: GetOidcUserInfo :one
|
||||
SELECT sub, name, preferred_username, email, "groups", updated_at FROM "oidc_userinfo"
|
||||
WHERE "sub" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) GetOidcUserInfo(ctx context.Context, sub string) (OidcUserinfo, error) {
|
||||
row := q.db.QueryRowContext(ctx, getOidcUserInfo, sub)
|
||||
var i OidcUserinfo
|
||||
err := row.Scan(
|
||||
&i.Sub,
|
||||
&i.Name,
|
||||
&i.PreferredUsername,
|
||||
&i.Email,
|
||||
&i.Groups,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateOidcTokenByRefreshToken = `-- name: UpdateOidcTokenByRefreshToken :one
|
||||
UPDATE "oidc_tokens" SET
|
||||
"access_token_hash" = ?,
|
||||
"refresh_token_hash" = ?,
|
||||
"token_expires_at" = ?,
|
||||
"refresh_token_expires_at" = ?
|
||||
WHERE "refresh_token_hash" = ?
|
||||
RETURNING sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at
|
||||
`
|
||||
|
||||
type UpdateOidcTokenByRefreshTokenParams struct {
|
||||
AccessTokenHash string
|
||||
RefreshTokenHash string
|
||||
TokenExpiresAt int64
|
||||
RefreshTokenExpiresAt int64
|
||||
RefreshTokenHash_2 string
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateOidcTokenByRefreshToken(ctx context.Context, arg UpdateOidcTokenByRefreshTokenParams) (OidcToken, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateOidcTokenByRefreshToken,
|
||||
arg.AccessTokenHash,
|
||||
arg.RefreshTokenHash,
|
||||
arg.TokenExpiresAt,
|
||||
arg.RefreshTokenExpiresAt,
|
||||
arg.RefreshTokenHash_2,
|
||||
)
|
||||
var i OidcToken
|
||||
err := row.Scan(
|
||||
&i.Sub,
|
||||
&i.AccessTokenHash,
|
||||
&i.RefreshTokenHash,
|
||||
&i.Scope,
|
||||
&i.ClientID,
|
||||
&i.TokenExpiresAt,
|
||||
&i.RefreshTokenExpiresAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// source: queries.sql
|
||||
// source: session_queries.sql
|
||||
|
||||
package repository
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
const createSession = `-- name: CreateSession :one
|
||||
INSERT INTO sessions (
|
||||
INSERT INTO "sessions" (
|
||||
"uuid",
|
||||
"username",
|
||||
"email",
|
||||
Reference in New Issue
Block a user