Compare commits
No commits in common. "f0011e183da862b4a1ad42923de9da2a0f46ee5d" and "c741400583264198cfde863a2f10d32e4a721ff0" have entirely different histories.
f0011e183d
...
c741400583
9 changed files with 12 additions and 182 deletions
|
@ -70,7 +70,6 @@ func serve() {
|
||||||
opConf := op.Config{
|
opConf := op.Config{
|
||||||
CryptoKey: key,
|
CryptoKey: key,
|
||||||
CodeMethodS256: false,
|
CodeMethodS256: false,
|
||||||
GrantTypeRefreshToken: true,
|
|
||||||
}
|
}
|
||||||
slogger := slog.New(zapslog.NewHandler(logger.L.Desugar().Core(), nil))
|
slogger := slog.New(zapslog.NewHandler(logger.L.Desugar().Core(), nil))
|
||||||
// slogger :=
|
// slogger :=
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/authrequest"
|
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/authrequest"
|
||||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/backend"
|
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/backend"
|
||||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/client"
|
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/client"
|
||||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/token"
|
|
||||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/user"
|
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,7 +19,6 @@ type Storage interface {
|
||||||
AuthRequestStorage() authrequest.AuthRequestDB
|
AuthRequestStorage() authrequest.AuthRequestDB
|
||||||
AuthCodeStorage() authcode.AuthCodeDB
|
AuthCodeStorage() authcode.AuthCodeDB
|
||||||
UserStorage() user.UserDB
|
UserStorage() user.UserDB
|
||||||
TokenStorage() token.TokenDB
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type sqlStorage struct {
|
type sqlStorage struct {
|
||||||
|
@ -51,10 +49,6 @@ func (s *sqlStorage) UserStorage() user.UserDB {
|
||||||
return user.New(s.db)
|
return user.New(s.db)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *sqlStorage) TokenStorage() token.TokenDB {
|
|
||||||
return token.New(s.db)
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(conf config.AppConfig) (Storage, error) {
|
func New(conf config.AppConfig) (Storage, error) {
|
||||||
db, err := sql.Open("sqlite3", conf.StorageConfig.File)
|
db, err := sql.Open("sqlite3", conf.StorageConfig.File)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
package token
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
func strArrayToSlice(rawVal string) []string {
|
|
||||||
var res []string
|
|
||||||
if err := json.Unmarshal([]byte(rawVal), &res); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func sliceToStrArray(rawVal []string) string {
|
|
||||||
res, err := json.Marshal(rawVal)
|
|
||||||
if err != nil {
|
|
||||||
return "[]"
|
|
||||||
}
|
|
||||||
return string(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TokenDB interface {
|
|
||||||
AddRefreshToken(ctx context.Context, refreshToken *model.RefreshToken) error
|
|
||||||
GetRefreshTokenByID(ctx context.Context, id uuid.UUID) (*model.RefreshToken, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type sqlTokenDB struct {
|
|
||||||
db *sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *sqlTokenDB) AddRefreshToken(ctx context.Context, refreshToken *model.RefreshToken) error {
|
|
||||||
tx, err := db.db.BeginTx(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to start transaction: %w", err)
|
|
||||||
}
|
|
||||||
defer func() { _ = tx.Rollback() }()
|
|
||||||
|
|
||||||
if _, err := tx.ExecContext(ctx, `INSERT INTO "refresh_token" ("id", "client_id", "user_id", "scopes", "auth_time") VALUES ($1, $2, $3, $4, $5)`, refreshToken.ID, refreshToken.ClientID, refreshToken.UserID, sliceToStrArray(refreshToken.Scopes), refreshToken.AuthTime); err != nil {
|
|
||||||
return fmt.Errorf("failed to exec query: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *sqlTokenDB) GetRefreshTokenByID(ctx context.Context, id uuid.UUID) (*model.RefreshToken, error) {
|
|
||||||
row := db.db.QueryRowContext(ctx, `SELECT "id", "client_id", "user_id", "scopes", "auth_time" FROM "refresh_token" WHERE "id" = ?`, id)
|
|
||||||
var res model.RefreshToken
|
|
||||||
var strScopes string
|
|
||||||
if err := row.Scan(&res.ID, &res.ClientID, &res.UserID, &strScopes, &res.AuthTime); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to query DB: %w", err)
|
|
||||||
}
|
|
||||||
res.Scopes = strArrayToSlice(strScopes)
|
|
||||||
return &res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(db *sql.DB) TokenDB {
|
|
||||||
return &sqlTokenDB{db: db}
|
|
||||||
}
|
|
|
@ -22,7 +22,7 @@ const getUserQuery = `
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`
|
`
|
||||||
const insertUserQuery = `
|
const insertUserQuery = `
|
||||||
INSERT OR REPLACE INTO user (id, name, family_name, given_name, nickname, picture, updated_at, email, email_verified)
|
INSERT INTO user (id, name, family_name, given_name, nickname, picture, updated_at, email, email_verified)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
|
@ -17,52 +17,5 @@ type Token struct {
|
||||||
|
|
||||||
type RefreshToken struct {
|
type RefreshToken struct {
|
||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
ClientID string
|
|
||||||
UserID string
|
|
||||||
Scopes []string
|
|
||||||
AuthTime time.Time
|
AuthTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t RefreshToken) Request() *RefreshTokenRequest {
|
|
||||||
return &RefreshTokenRequest{
|
|
||||||
userID: t.UserID,
|
|
||||||
clientID: t.ClientID,
|
|
||||||
scopes: t.Scopes,
|
|
||||||
authTime: t.AuthTime,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type RefreshTokenRequest struct {
|
|
||||||
clientID string
|
|
||||||
authTime time.Time
|
|
||||||
userID string
|
|
||||||
scopes []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r RefreshTokenRequest) GetAMR() []string {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r RefreshTokenRequest) GetAudience() []string {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r RefreshTokenRequest) GetAuthTime() time.Time {
|
|
||||||
return r.authTime
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r RefreshTokenRequest) GetClientID() string {
|
|
||||||
return r.clientID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r RefreshTokenRequest) GetScopes() []string {
|
|
||||||
return r.scopes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r RefreshTokenRequest) GetSubject() string {
|
|
||||||
return r.userID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RefreshTokenRequest) SetCurrentScopes(scopes []string) {
|
|
||||||
r.scopes = scopes
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
@ -136,20 +135,14 @@ func (s *Storage) DeleteAuthRequest(ctx context.Context, id string) error {
|
||||||
|
|
||||||
func (s *Storage) CreateAccessToken(ctx context.Context, req op.TokenRequest) (accessTokenID string, expiration time.Time, err error) {
|
func (s *Storage) CreateAccessToken(ctx context.Context, req op.TokenRequest) (accessTokenID string, expiration time.Time, err error) {
|
||||||
accessTokenUUID := uuid.New()
|
accessTokenUUID := uuid.New()
|
||||||
var authTime time.Time
|
|
||||||
|
|
||||||
switch typedReq := req.(type) {
|
// we are expecting our own request model
|
||||||
case *model.AuthRequest:
|
authRequest, ok := req.(*model.AuthRequest)
|
||||||
logger.L.Debug("Creating access token for new authentication")
|
if !ok {
|
||||||
authTime = typedReq.AuthTime
|
|
||||||
case *model.RefreshTokenRequest:
|
|
||||||
logger.L.Debug("Handling refresh token request")
|
|
||||||
authTime = typedReq.GetAuthTime()
|
|
||||||
default:
|
|
||||||
logger.L.Errorf("Unexpected type for request %v", err)
|
|
||||||
return "", time.Time{}, errors.New("failed to parse auth request")
|
return "", time.Time{}, errors.New("failed to parse auth request")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authTime := authRequest.AuthTime.UTC()
|
||||||
expiration = authTime.Add(5 * time.Minute)
|
expiration = authTime.Add(5 * time.Minute)
|
||||||
|
|
||||||
// token := model.Token{
|
// token := model.Token{
|
||||||
|
@ -167,23 +160,14 @@ func (s *Storage) CreateAccessToken(ctx context.Context, req op.TokenRequest) (a
|
||||||
func (s *Storage) CreateAccessAndRefreshTokens(ctx context.Context, request op.TokenRequest, currentRefreshToken string) (accessTokenID string, newRefreshTokenID string, expiration time.Time, err error) {
|
func (s *Storage) CreateAccessAndRefreshTokens(ctx context.Context, request op.TokenRequest, currentRefreshToken string) (accessTokenID string, newRefreshTokenID string, expiration time.Time, err error) {
|
||||||
accessTokenUUID := uuid.New()
|
accessTokenUUID := uuid.New()
|
||||||
refreshTokenUUID := uuid.New()
|
refreshTokenUUID := uuid.New()
|
||||||
var authTime time.Time
|
|
||||||
var clientID string
|
|
||||||
|
|
||||||
switch typedReq := request.(type) {
|
// we are expecting our own request model
|
||||||
case *model.AuthRequest:
|
authRequest, ok := request.(*model.AuthRequest)
|
||||||
logger.L.Debug("Creating access token for new authentication")
|
if !ok {
|
||||||
clientID = typedReq.ClientID
|
|
||||||
authTime = typedReq.AuthTime
|
|
||||||
case *model.RefreshTokenRequest:
|
|
||||||
logger.L.Debug("Handling refresh token request")
|
|
||||||
clientID = typedReq.GetClientID()
|
|
||||||
authTime = typedReq.GetAuthTime()
|
|
||||||
default:
|
|
||||||
logger.L.Errorf("Unexpected type for request %v", err)
|
|
||||||
return "", "", time.Time{}, errors.New("failed to parse auth request")
|
return "", "", time.Time{}, errors.New("failed to parse auth request")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authTime := authRequest.AuthTime.UTC()
|
||||||
expiration = authTime.Add(5 * time.Minute)
|
expiration = authTime.Add(5 * time.Minute)
|
||||||
|
|
||||||
// token := model.Token{
|
// token := model.Token{
|
||||||
|
@ -194,34 +178,12 @@ func (s *Storage) CreateAccessAndRefreshTokens(ctx context.Context, request op.T
|
||||||
// Audiences: request.GetAudience(),
|
// Audiences: request.GetAudience(),
|
||||||
// Scopes: request.GetScopes(),
|
// Scopes: request.GetScopes(),
|
||||||
// }
|
// }
|
||||||
refreshToken := model.RefreshToken{
|
|
||||||
ID: refreshTokenUUID,
|
|
||||||
ClientID: clientID,
|
|
||||||
UserID: request.GetSubject(),
|
|
||||||
Scopes: request.GetScopes(),
|
|
||||||
AuthTime: authTime,
|
|
||||||
}
|
|
||||||
if err := s.LocalStorage.TokenStorage().AddRefreshToken(ctx, &refreshToken); err != nil {
|
|
||||||
return "", "", time.Time{}, fmt.Errorf("failed to insert token in DB: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return accessTokenUUID.String(), refreshTokenUUID.String(), expiration, nil
|
return accessTokenUUID.String(), refreshTokenUUID.String(), expiration, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Storage) TokenRequestByRefreshToken(ctx context.Context, refreshTokenID string) (op.RefreshTokenRequest, error) {
|
func (s *Storage) TokenRequestByRefreshToken(ctx context.Context, refreshTokenID string) (op.RefreshTokenRequest, error) {
|
||||||
parsedID, err := uuid.Parse(refreshTokenID)
|
return nil, ErrNotImplemented("TokenRequestByRefreshToken")
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid format for refresh token id: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshToken, err := s.LocalStorage.TokenStorage().GetRefreshTokenByID(ctx, parsedID)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return nil, op.ErrInvalidRefreshToken
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("failed to get refresh token: %w", err)
|
|
||||||
}
|
|
||||||
return refreshToken.Request(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Storage) TerminateSession(ctx context.Context, userID string, clientID string) error {
|
func (s *Storage) TerminateSession(ctx context.Context, userID string, clientID string) error {
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
DROP TABLE refresh_token;
|
|
|
@ -1,9 +0,0 @@
|
||||||
CREATE TABLE refresh_token (
|
|
||||||
id TEXT NOT NULL PRIMARY KEY,
|
|
||||||
client_id TEXT NOT NULL,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
scopes blob NOT NULL, -- list of strings, json-encoded
|
|
||||||
auth_time timestamp NOT NULL,
|
|
||||||
FOREIGN KEY(client_id) REFERENCES client(id),
|
|
||||||
FOREIGN KEY(user_id) REFERENCES user(id)
|
|
||||||
);
|
|
Binary file not shown.
Loading…
Reference in a new issue