sqlc removed and adding sessionToken to jwt
This commit is contained in:
parent
4e9a17209f
commit
47058dd866
42
docs/docs.go
42
docs/docs.go
@ -996,6 +996,45 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/users/refresh/sessionToken": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Revokes the current session token and replaces it with a new one.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.BaseResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.BaseResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.BaseResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/users/refreshToken": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -1311,6 +1350,9 @@ const docTemplate = `{
|
||||
"refreshToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"sessionToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"token": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -987,6 +987,45 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/users/refresh/sessionToken": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Revokes the current session token and replaces it with a new one.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.BaseResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.BaseResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.BaseResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/users/refreshToken": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -1302,6 +1341,9 @@
|
||||
"refreshToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"sessionToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"token": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -84,6 +84,8 @@ definitions:
|
||||
type: string
|
||||
refreshToken:
|
||||
type: string
|
||||
sessionToken:
|
||||
type: string
|
||||
token:
|
||||
type: string
|
||||
type:
|
||||
@ -750,6 +752,30 @@ paths:
|
||||
summary: Logs into the API and returns a bearer token if successful
|
||||
tags:
|
||||
- Users
|
||||
/v1/users/refresh/sessionToken:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.BaseResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.BaseResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.BaseResponse'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Revokes the current session token and replaces it with a new one.
|
||||
tags:
|
||||
- Users
|
||||
/v1/users/refreshToken:
|
||||
post:
|
||||
parameters:
|
||||
|
@ -10,6 +10,7 @@ type LoginResponse struct {
|
||||
Token string `json:"token"`
|
||||
Type string `json:"type"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
SessionToken string `json:"sessionToken"`
|
||||
}
|
||||
|
||||
type ArticleResponse struct {
|
||||
|
@ -1,31 +0,0 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.16.0
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type DBTX interface {
|
||||
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
|
||||
PrepareContext(context.Context, string) (*sql.Stmt, error)
|
||||
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
|
||||
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
|
||||
}
|
||||
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
db DBTX
|
||||
}
|
||||
|
||||
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
|
||||
return &Queries{
|
||||
db: tx,
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type SourceDto struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Site string `json:"site"`
|
||||
Name string `json:"name"`
|
||||
Source string `json:"source"`
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Url string `json:"url"`
|
||||
Tags []string `json:"tags"`
|
||||
Deleted bool `json:"deleted"`
|
||||
}
|
||||
|
||||
func ConvertToSourceDto(i Source) SourceDto {
|
||||
var deleted bool
|
||||
if !i.Deleted.Valid {
|
||||
deleted = true
|
||||
}
|
||||
|
||||
return SourceDto{
|
||||
ID: i.ID,
|
||||
Site: i.Site,
|
||||
Name: i.Name,
|
||||
Source: i.Source,
|
||||
Type: i.Type,
|
||||
Value: i.Value.String,
|
||||
Enabled: i.Enabled,
|
||||
Url: i.Url,
|
||||
Tags: splitTags(i.Tags),
|
||||
Deleted: deleted,
|
||||
}
|
||||
}
|
||||
|
||||
func splitTags(t string) []string {
|
||||
items := strings.Split(t, ", ")
|
||||
return items
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE Users ADD SessionToken TEXT;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE Users DROP SessionToken;
|
||||
-- +goose StatementEnd
|
@ -1,73 +0,0 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.16.0
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Article struct {
|
||||
ID uuid.UUID
|
||||
Sourceid uuid.UUID
|
||||
Tags string
|
||||
Title string
|
||||
Url string
|
||||
Pubdate time.Time
|
||||
Video sql.NullString
|
||||
Videoheight int32
|
||||
Videowidth int32
|
||||
Thumbnail string
|
||||
Description string
|
||||
Authorname sql.NullString
|
||||
Authorimage sql.NullString
|
||||
}
|
||||
|
||||
type Discordqueue struct {
|
||||
ID uuid.UUID
|
||||
Articleid uuid.UUID
|
||||
}
|
||||
|
||||
type Discordwebhook struct {
|
||||
ID uuid.UUID
|
||||
Url string
|
||||
Server string
|
||||
Channel string
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
type Icon struct {
|
||||
ID uuid.UUID
|
||||
Filename string
|
||||
Site string
|
||||
}
|
||||
|
||||
type Setting struct {
|
||||
ID uuid.UUID
|
||||
Key string
|
||||
Value string
|
||||
Options sql.NullString
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
ID uuid.UUID
|
||||
Site string
|
||||
Name string
|
||||
Source string
|
||||
Type string
|
||||
Value sql.NullString
|
||||
Enabled bool
|
||||
Url string
|
||||
Tags string
|
||||
Deleted sql.NullBool
|
||||
}
|
||||
|
||||
type Subscription struct {
|
||||
ID uuid.UUID
|
||||
Discordwebhookid uuid.UUID
|
||||
Sourceid uuid.UUID
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,215 +0,0 @@
|
||||
/* Articles */
|
||||
-- name: GetArticleByID :one
|
||||
Select * from Articles
|
||||
WHERE ID = $1 LIMIT 1;
|
||||
|
||||
-- name: GetArticleByUrl :one
|
||||
Select * from Articles
|
||||
Where Url = $1 LIMIT 1;
|
||||
|
||||
-- name: ListArticles :many
|
||||
Select * From articles
|
||||
Order By PubDate DESC
|
||||
offset $2
|
||||
fetch next $1 rows only;
|
||||
|
||||
-- name: ListArticlesByDate :many
|
||||
Select * From articles
|
||||
ORDER BY pubdate desc
|
||||
Limit $1;
|
||||
|
||||
-- name: GetArticlesBySource :many
|
||||
select * from articles
|
||||
INNER join sources on articles.sourceid=Sources.ID
|
||||
where site = $1;
|
||||
|
||||
-- name: ListNewArticlesBySourceId :many
|
||||
SELECT * FROM articles
|
||||
Where sourceid = $1
|
||||
ORDER BY pubdate desc
|
||||
offset $3
|
||||
fetch next $2 rows only;
|
||||
|
||||
-- name: ListOldestArticlesBySourceId :many
|
||||
SELECT * FROM articles
|
||||
Where sourceid = $1
|
||||
ORDER BY pubdate asc
|
||||
offset $3
|
||||
fetch next $2 rows only;
|
||||
|
||||
|
||||
-- name: ListArticlesBySourceId :many
|
||||
Select * From articles
|
||||
Where sourceid = $1
|
||||
Limit 50;
|
||||
|
||||
-- name: GetArticlesBySourceName :many
|
||||
select
|
||||
articles.ID, articles.SourceId, articles.Tags, articles.Title, articles.Url, articles.PubDate, articles.Video, articles.VideoHeight, articles.VideoWidth, articles.Thumbnail, articles.Description, articles.AuthorName, articles.AuthorImage, sources.source, sources.name
|
||||
From articles
|
||||
Left Join sources
|
||||
On articles.sourceid = sources.id
|
||||
Where name = $1;
|
||||
|
||||
-- name: ListArticlesByPage :many
|
||||
select * from articles
|
||||
order by pubdate desc
|
||||
offset $2
|
||||
fetch next $1 rows only;
|
||||
|
||||
-- name: CreateArticle :exec
|
||||
INSERT INTO Articles
|
||||
(ID, SourceId, Tags, Title, Url, PubDate, Video, VideoHeight, VideoWidth, Thumbnail, Description, AuthorName, AuthorImage)
|
||||
Values
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);
|
||||
|
||||
|
||||
/* DiscordQueue */
|
||||
-- name: CreateDiscordQueue :exec
|
||||
Insert into DiscordQueue
|
||||
(ID, ArticleId)
|
||||
Values
|
||||
($1, $2);
|
||||
|
||||
-- name: GetDiscordQueueByID :one
|
||||
Select * from DiscordQueue
|
||||
Where ID = $1 LIMIT 1;
|
||||
|
||||
-- name: DeleteDiscordQueueItem :exec
|
||||
Delete From DiscordQueue Where ID = $1;
|
||||
|
||||
-- name: ListDiscordQueueItems :many
|
||||
Select * from DiscordQueue LIMIT $1;
|
||||
|
||||
/* DiscordWebHooks */
|
||||
-- name: CreateDiscordWebHook :exec
|
||||
Insert Into DiscordWebHooks
|
||||
(ID, Url, Server, Channel, Enabled)
|
||||
Values
|
||||
($1, $2, $3, $4, $5);
|
||||
|
||||
-- name: GetDiscordWebHooksByID :one
|
||||
Select * from DiscordWebHooks
|
||||
Where ID = $1 LIMIT 1;
|
||||
|
||||
-- name: ListDiscordWebHooksByServer :many
|
||||
Select * From DiscordWebHooks
|
||||
Where Server = $1;
|
||||
|
||||
-- name: GetDiscordWebHooksByServerAndChannel :many
|
||||
SELECT * FROM DiscordWebHooks
|
||||
WHERE Server = $1 and Channel = $2;
|
||||
|
||||
-- name: GetDiscordWebHookByUrl :one
|
||||
Select * From DiscordWebHooks Where url = $1;
|
||||
|
||||
-- name: ListDiscordWebhooks :many
|
||||
Select * From discordwebhooks LIMIT $1;
|
||||
|
||||
-- name: DeleteDiscordWebHooks :exec
|
||||
Delete From discordwebhooks Where ID = $1;
|
||||
|
||||
-- name: DisableDiscordWebHook :exec
|
||||
Update discordwebhooks Set Enabled = FALSE where ID = $1;
|
||||
|
||||
-- name: EnableDiscordWebHook :exec
|
||||
Update discordwebhooks Set Enabled = TRUE where ID = $1;
|
||||
|
||||
/* Icons */
|
||||
|
||||
-- name: CreateIcon :exec
|
||||
INSERT INTO Icons
|
||||
(ID, FileName, Site)
|
||||
VALUES
|
||||
($1,$2,$3);
|
||||
|
||||
-- name: GetIconByID :one
|
||||
Select * FROM Icons
|
||||
Where ID = $1 Limit 1;
|
||||
|
||||
-- name: GetIconBySite :one
|
||||
Select * FROM Icons
|
||||
Where Site = $1 Limit 1;
|
||||
|
||||
-- name: DeleteIcon :exec
|
||||
Delete From Icons where ID = $1;
|
||||
|
||||
/* Settings */
|
||||
|
||||
-- name: CreateSettings :one
|
||||
Insert Into settings
|
||||
(ID, Key, Value, OPTIONS)
|
||||
Values
|
||||
($1,$2,$3,$4)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetSettingByID :one
|
||||
Select * From settings
|
||||
Where ID = $1 Limit 1;
|
||||
|
||||
-- name: GetSettingByKey :one
|
||||
Select * From settings Where
|
||||
Key = $1 Limit 1;
|
||||
|
||||
-- name: GetSettingByValue :one
|
||||
Select * From settings Where
|
||||
Value = $1 Limit 1;
|
||||
|
||||
-- name: DeleteSetting :exec
|
||||
Delete From settings Where ID = $1;
|
||||
|
||||
/* Sources */
|
||||
|
||||
-- name: CreateSource :exec
|
||||
Insert Into Sources
|
||||
(ID, Site, Name, Source, Type, Value, Enabled, Url, Tags)
|
||||
Values
|
||||
($1,$2,$3,$4,$5,$6,$7,$8,$9);
|
||||
|
||||
-- name: GetSourceByID :one
|
||||
Select * From Sources where ID = $1 Limit 1;
|
||||
|
||||
-- name: GetSourceByName :one
|
||||
Select * from Sources where name = $1 Limit 1;
|
||||
|
||||
-- name: GetSourceByNameAndSource :one
|
||||
Select * from Sources WHERE name = $1 and source = $2;
|
||||
|
||||
-- name: ListSources :many
|
||||
Select * From Sources Limit $1;
|
||||
|
||||
-- name: ListSourcesBySource :many
|
||||
Select * From Sources where Source = $1;
|
||||
|
||||
-- name: DeleteSource :exec
|
||||
UPDATE Sources Set Disabled = TRUE where id = $1;
|
||||
|
||||
-- name: DisableSource :exec
|
||||
Update Sources Set Enabled = FALSE where ID = $1;
|
||||
|
||||
-- name: EnableSource :exec
|
||||
Update Sources Set Enabled = TRUE where ID = $1;
|
||||
|
||||
|
||||
/* Subscriptions */
|
||||
|
||||
-- name: CreateSubscription :exec
|
||||
Insert Into subscriptions (ID, DiscordWebHookId, SourceId) Values ($1, $2, $3);
|
||||
|
||||
-- name: ListSubscriptions :many
|
||||
Select * From subscriptions Limit $1;
|
||||
|
||||
-- name: ListSubscriptionsBySourceId :many
|
||||
Select * From subscriptions where sourceid = $1;
|
||||
|
||||
-- name: QuerySubscriptions :one
|
||||
Select * From subscriptions Where discordwebhookid = $1 and sourceid = $2 Limit 1;
|
||||
|
||||
-- name: GetSubscriptionsBySourceID :many
|
||||
Select * From subscriptions Where sourceid = $1;
|
||||
|
||||
-- name: GetSubscriptionsByDiscordWebHookId :many
|
||||
Select * from subscriptions Where discordwebhookid = $1;
|
||||
|
||||
-- name: DeleteSubscription :exec
|
||||
Delete From subscriptions Where id = $1;
|
@ -1,61 +0,0 @@
|
||||
CREATE TABLE Articles (
|
||||
ID uuid PRIMARY KEY,
|
||||
SourceId uuid NOT null,
|
||||
Tags TEXT NOT NULL,
|
||||
Title TEXT NOT NULL,
|
||||
Url TEXT NOT NULL,
|
||||
PubDate timestamp NOT NULL,
|
||||
Video TEXT,
|
||||
VideoHeight int NOT NULL,
|
||||
VideoWidth int NOT NULL,
|
||||
Thumbnail TEXT NOT NULL,
|
||||
Description TEXT NOT NULL,
|
||||
AuthorName TEXT,
|
||||
AuthorImage TEXT
|
||||
);
|
||||
|
||||
CREATE Table DiscordQueue (
|
||||
ID uuid PRIMARY KEY,
|
||||
ArticleId uuid NOT NULL
|
||||
);
|
||||
|
||||
CREATE Table DiscordWebHooks (
|
||||
ID uuid PRIMARY KEY,
|
||||
Url TEXT NOT NULL, -- Webhook Url
|
||||
Server TEXT NOT NULL, -- Defines the server its bound it. Used for refrence
|
||||
Channel TEXT NOT NULL, -- Defines the channel its bound to. Used for refrence
|
||||
Enabled BOOLEAN NOT NULL
|
||||
);
|
||||
|
||||
CREATE Table Icons (
|
||||
ID uuid PRIMARY Key,
|
||||
FileName TEXT NOT NULL,
|
||||
Site TEXT NOT NULL
|
||||
);
|
||||
|
||||
Create Table Settings (
|
||||
ID uuid PRIMARY Key,
|
||||
Key TEXT NOT NULL,
|
||||
Value TEXT NOT NULL,
|
||||
Options TEXT
|
||||
);
|
||||
|
||||
Create Table Sources (
|
||||
ID uuid PRIMARY Key,
|
||||
Site TEXT NOT NULL, -- Vanity name
|
||||
Name TEXT NOT NULL, -- Defines the name of the source. IE: dadjokes
|
||||
Source TEXT NOT NULL, -- Defines the service that will use this reocrd. IE reddit or youtube
|
||||
Type TEXT NOT NULL, -- Defines what kind of feed this is. feed, user, tag
|
||||
Value TEXT,
|
||||
Enabled BOOLEAN NOT NULL,
|
||||
Url TEXT NOT NULL,
|
||||
Tags TEXT NOT NULL,
|
||||
Deleted BOOLEAN
|
||||
);
|
||||
|
||||
/* This table is used to track what the Web Hook wants to have sent by Source */;
|
||||
Create TABLE Subscriptions (
|
||||
ID uuid Primary Key,
|
||||
DiscordWebHookID uuid Not Null,
|
||||
SourceID uuid Not Null
|
||||
);
|
@ -121,13 +121,14 @@ type UserSourceSubscriptionEntity struct {
|
||||
}
|
||||
|
||||
type UserEntity struct {
|
||||
ID int64
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt time.Time
|
||||
Username string
|
||||
Hash string
|
||||
Scopes string
|
||||
ID int64
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt time.Time
|
||||
Username string
|
||||
Hash string
|
||||
Scopes string
|
||||
SessionToken string
|
||||
}
|
||||
|
||||
type RefreshTokenEntity struct {
|
||||
|
@ -83,11 +83,11 @@ func (j JwtToken) hasScope(scope string) error {
|
||||
return errors.New(ErrJwtScopeMissing)
|
||||
}
|
||||
|
||||
func (h *Handler) generateJwt(username, issuer string, userScopes []string, userId int64) (string, error) {
|
||||
return h.generateJwtWithExp(username, issuer, userScopes, userId, time.Now().Add(10*time.Minute))
|
||||
func (h *Handler) generateJwt(username, issuer, sessionToken string, userScopes []string, userId int64) (string, error) {
|
||||
return h.generateJwtWithExp(username, issuer, sessionToken, userScopes, userId, time.Now().Add(10*time.Minute))
|
||||
}
|
||||
|
||||
func (h *Handler) generateJwtWithExp(username, issuer string, userScopes []string, userId int64, expiresAt time.Time) (string, error) {
|
||||
func (h *Handler) generateJwtWithExp(username, issuer, sessionToken string, userScopes []string, userId int64, expiresAt time.Time) (string, error) {
|
||||
secret := []byte(h.config.JwtSecret)
|
||||
|
||||
// Anyone who wants to decrypt the key needs to use the same method
|
||||
@ -98,6 +98,7 @@ func (h *Handler) generateJwtWithExp(username, issuer string, userScopes []strin
|
||||
claims["username"] = username
|
||||
claims["iss"] = issuer
|
||||
claims["userId"] = userId
|
||||
claims["sessionToken"] = sessionToken
|
||||
|
||||
var scopes []string
|
||||
scopes = append(scopes, userScopes...)
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
@ -48,7 +49,7 @@ func (h *Handler) AuthRegister(c echo.Context) error {
|
||||
return h.WriteError(c, err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
_, err = h.repo.Users.Create(c.Request().Context(), username, password, domain.ScopeArticleRead)
|
||||
_, err = h.repo.Users.Create(c.Request().Context(), username, password, "", domain.ScopeArticleRead)
|
||||
if err != nil {
|
||||
return h.InternalServerErrorResponse(c, err.Error())
|
||||
}
|
||||
@ -91,8 +92,12 @@ func (h *Handler) AuthLogin(c echo.Context) error {
|
||||
// TODO think about moving this down some?
|
||||
expiresAt := time.Now().Add(time.Hour * 48)
|
||||
userScopes := strings.Split(user.Scopes, ",")
|
||||
sessionToken, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
return h.InternalServerErrorResponse(c, err.Error())
|
||||
}
|
||||
|
||||
jwt, err := h.generateJwtWithExp(username, h.config.ServerAddress, userScopes, user.ID, expiresAt)
|
||||
jwt, err := h.generateJwtWithExp(username, h.config.ServerAddress, sessionToken.String(), userScopes, user.ID, expiresAt)
|
||||
if err != nil {
|
||||
return h.InternalServerErrorResponse(c, err.Error())
|
||||
}
|
||||
@ -109,6 +114,7 @@ func (h *Handler) AuthLogin(c echo.Context) error {
|
||||
Token: jwt,
|
||||
Type: "Bearer",
|
||||
RefreshToken: refresh,
|
||||
SessionToken: sessionToken.String(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -124,8 +130,12 @@ func (h *Handler) createAdminToken(c echo.Context, password string) error {
|
||||
}
|
||||
var userScopes []string
|
||||
userScopes = append(userScopes, domain.ScopeAll)
|
||||
sessionToken, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
return h.InternalServerErrorResponse(c, err.Error())
|
||||
}
|
||||
|
||||
token, err := h.generateJwt("admin", h.config.ServerAddress, userScopes, -1)
|
||||
token, err := h.generateJwt("admin", h.config.ServerAddress, sessionToken.String(), userScopes, -1)
|
||||
if err != nil {
|
||||
return h.InternalServerErrorResponse(c, err.Error())
|
||||
}
|
||||
@ -173,7 +183,7 @@ func (h *Handler) RefreshJwtToken(c echo.Context) error {
|
||||
}
|
||||
userScopes := strings.Split(user.Scopes, ",")
|
||||
|
||||
jwt, err := h.generateJwtWithExp(request.Username, h.config.ServerAddress, userScopes, user.ID, time.Now().Add(time.Hour*48))
|
||||
jwt, err := h.generateJwtWithExp(request.Username, h.config.ServerAddress, "", userScopes, user.ID, time.Now().Add(time.Hour*48))
|
||||
if err != nil {
|
||||
return h.InternalServerErrorResponse(c, err.Error())
|
||||
}
|
||||
@ -198,10 +208,10 @@ func (h *Handler) RefreshJwtToken(c echo.Context) error {
|
||||
// @Param request body domain.UpdateScopesRequest true "body"
|
||||
// @Tags Users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Produce json
|
||||
// @Success 200 {object} domain.BaseResponse
|
||||
// @Failure 400 {object} domain.BaseResponse
|
||||
// @Failure 500 {object} domain.BaseResponse
|
||||
// @Failure 400 {object} domain.BaseResponse
|
||||
// @Failure 500 {object} domain.BaseResponse
|
||||
// @Security Bearer
|
||||
func (h *Handler) AddScopes(c echo.Context) error {
|
||||
_, err := h.ValidateJwtToken(c, domain.ScopeAll)
|
||||
@ -231,7 +241,7 @@ func (h *Handler) AddScopes(c echo.Context) error {
|
||||
// @Tags Users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} domain.BaseResponse
|
||||
// @Success 200 {object} domain.BaseResponse
|
||||
// @Failure 400 {object} domain.BaseResponse
|
||||
// @Failure 500 {object} domain.BaseResponse
|
||||
// @Security Bearer
|
||||
@ -250,7 +260,6 @@ func (h *Handler) RemoveScopes(c echo.Context) error {
|
||||
err = (&echo.DefaultBinder{}).BindBody(c, &request)
|
||||
if err != nil {
|
||||
h.WriteError(c, err, http.StatusBadRequest)
|
||||
|
||||
}
|
||||
|
||||
err = h.repo.Users.RemoveScopes(c.Request().Context(), request.Username, request.Scopes)
|
||||
@ -262,3 +271,26 @@ func (h *Handler) RemoveScopes(c echo.Context) error {
|
||||
Message: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary Revokes the current session token and replaces it with a new one.
|
||||
// @Router /v1/users/refresh/sessionToken [post]
|
||||
// @Tags Users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} domain.BaseResponse
|
||||
// @Failure 400 {object} domain.BaseResponse
|
||||
// @Failure 500 {object} domain.BaseResponse
|
||||
// @Security Bearer
|
||||
func (h *Handler) LogoutEverywhere(c echo.Context) error {
|
||||
token, err := h.getJwtTokenFromContext(c)
|
||||
if err != nil {
|
||||
return h.WriteError(c, err, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
err = token.IsValid(domain.ScopeAll)
|
||||
if err != nil {
|
||||
return h.WriteError(c, err, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
return c.String(http.StatusInternalServerError, "Not Implemented")
|
||||
}
|
@ -14,17 +14,18 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
TableName string = "users"
|
||||
usersTableName string = "users"
|
||||
ErrUserNotFound string = "requested user was not found"
|
||||
)
|
||||
|
||||
type Users interface {
|
||||
GetByName(ctx context.Context, name string) (entity.UserEntity, error)
|
||||
Create(ctx context.Context, name, password, scope string) (int64, error)
|
||||
Create(ctx context.Context, name, password, sessionTOken, scope string) (int64, error)
|
||||
Update(ctx context.Context, id int, entity entity.UserEntity) error
|
||||
UpdatePassword(ctx context.Context, name, password string) error
|
||||
CheckUserHash(ctx context.Context, name, password string) error
|
||||
UpdateScopes(ctx context.Context, name, scope string) error
|
||||
UpdateSessionToken(ctx context.Context, name, sessionToken string) (int64, error)
|
||||
}
|
||||
|
||||
// Creates a new instance of UserRepository with the bound sql
|
||||
@ -58,7 +59,7 @@ func (ur userRepository) GetByName(ctx context.Context, name string) (entity.Use
|
||||
return data[0], nil
|
||||
}
|
||||
|
||||
func (ur userRepository) Create(ctx context.Context, name, password, scope string) (int64, error) {
|
||||
func (ur userRepository) Create(ctx context.Context, name, password, sessionToken, scope string) (int64, error) {
|
||||
passwordBytes := []byte(password)
|
||||
hash, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
@ -68,8 +69,8 @@ func (ur userRepository) Create(ctx context.Context, name, password, scope strin
|
||||
dt := time.Now()
|
||||
queryBuilder := sqlbuilder.NewInsertBuilder()
|
||||
queryBuilder.InsertInto("users")
|
||||
queryBuilder.Cols("Name", "Hash", "UpdatedAt", "CreatedAt", "DeletedAt", "Scopes")
|
||||
queryBuilder.Values(name, string(hash), dt, dt, time.Time{}, scope)
|
||||
queryBuilder.Cols("Name", "Hash", "UpdatedAt", "CreatedAt", "DeletedAt", "Scopes", "SessionToken")
|
||||
queryBuilder.Values(name, string(hash), dt, dt, time.Time{}, scope, sessionToken)
|
||||
query, args := queryBuilder.Build()
|
||||
|
||||
_, err = ur.connection.ExecContext(ctx, query, args...)
|
||||
@ -91,11 +92,35 @@ func (ur userRepository) UpdatePassword(ctx context.Context, name, password stri
|
||||
}
|
||||
|
||||
queryBuilder := sqlbuilder.NewUpdateBuilder()
|
||||
queryBuilder.Update(TableName)
|
||||
queryBuilder.Update(usersTableName)
|
||||
//queryBuilder.Set
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ur userRepository) UpdateSessionToken(ctx context.Context, name, sessionToken string) (int64, error) {
|
||||
_, err := ur.GetByName(ctx, name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
q := sqlbuilder.NewUpdateBuilder()
|
||||
q.Update(usersTableName)
|
||||
q.Set(
|
||||
q.Equal("SessionToken", sessionToken),
|
||||
)
|
||||
q.Where(
|
||||
q.Equal("Name", name),
|
||||
)
|
||||
|
||||
query, args := q.Build()
|
||||
rowsUpdates, err := ur.connection.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return rowsUpdates.RowsAffected()
|
||||
}
|
||||
|
||||
// If the hash matches what we have in the database, an error will not be returned.
|
||||
// If the user does not exist or the hash does not match, an error will be returned
|
||||
func (ur userRepository) CheckUserHash(ctx context.Context, name, password string) error {
|
||||
@ -141,18 +166,20 @@ func (ur userRepository) processRows(rows *sql.Rows) []entity.UserEntity {
|
||||
var updatedAt time.Time
|
||||
var deletedAt sql.NullTime
|
||||
var scopes string
|
||||
err := rows.Scan(&id, &createdAt, &updatedAt, &deletedAt, &username, &hash, &scopes)
|
||||
var sessionToken string
|
||||
err := rows.Scan(&id, &createdAt, &updatedAt, &deletedAt, &username, &hash, &scopes, &sessionToken)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
item := entity.UserEntity{
|
||||
ID: id,
|
||||
UpdatedAt: updatedAt,
|
||||
Username: username,
|
||||
Hash: hash,
|
||||
Scopes: scopes,
|
||||
CreatedAt: createdAt,
|
||||
ID: id,
|
||||
UpdatedAt: updatedAt,
|
||||
Username: username,
|
||||
Hash: hash,
|
||||
Scopes: scopes,
|
||||
CreatedAt: createdAt,
|
||||
SessionToken: sessionToken,
|
||||
}
|
||||
if deletedAt.Valid {
|
||||
item.DeletedAt = deletedAt.Time
|
||||
|
@ -21,7 +21,7 @@ func TestCanCreateNewUser(t *testing.T) {
|
||||
defer db.Close()
|
||||
|
||||
repo := repository.NewUserRepository(db)
|
||||
updated, err := repo.Create(context.Background(), "testing", "NotSecure", "placeholder")
|
||||
updated, err := repo.Create(context.Background(), "testing", "NotSecure", "sessionToken", "placeholder")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
t.FailNow()
|
||||
@ -38,7 +38,7 @@ func TestCanFindUserInTable(t *testing.T) {
|
||||
defer db.Close()
|
||||
|
||||
repo := repository.NewUserRepository(db)
|
||||
updated, err := repo.Create(context.Background(), "testing", "NotSecure", "placeholder")
|
||||
updated, err := repo.Create(context.Background(), "testing", "NotSecure", "sessionToken", "placeholder")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
|
@ -4,11 +4,13 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.jamestombleson.com/jtom38/newsbot-api/domain"
|
||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/repository"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
@ -25,7 +27,8 @@ type UserServices interface {
|
||||
GetUser(ctx context.Context, username string) (entity.UserEntity, error)
|
||||
AddScopes(ctx context.Context, username string, scopes []string) error
|
||||
RemoveScopes(ctx context.Context, username string, scopes []string) error
|
||||
Create(ctx context.Context, name, password, scope string) (entity.UserEntity, error)
|
||||
Create(ctx context.Context, name, password, sessionToken, scope string) (entity.UserEntity, error)
|
||||
NewSessionToken(ctx context.Context, name string) error
|
||||
CheckPasswordForRequirements(password string) error
|
||||
}
|
||||
|
||||
@ -125,16 +128,34 @@ func (us UserService) doesScopeExist(scopes []string, target string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (us UserService) Create(ctx context.Context, name, password, scope string) (entity.UserEntity, error) {
|
||||
func (us UserService) Create(ctx context.Context, name, password, sessionToken, scope string) (entity.UserEntity, error) {
|
||||
err := us.CheckPasswordForRequirements(password)
|
||||
if err != nil {
|
||||
return entity.UserEntity{}, err
|
||||
}
|
||||
|
||||
us.repo.Create(ctx, name, password, domain.ScopeArticleRead)
|
||||
us.repo.Create(ctx, name, password, sessionToken, domain.ScopeArticleRead)
|
||||
return entity.UserEntity{}, nil
|
||||
}
|
||||
|
||||
func (us UserService) NewSessionToken(ctx context.Context, name string) error {
|
||||
token, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows, err := us.repo.UpdateSessionToken(ctx, name, token.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rows != 1 {
|
||||
return fmt.Errorf("UserService.NewSessionToken %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (us UserService) CheckPasswordForRequirements(password string) error {
|
||||
err := us.checkPasswordLength(password)
|
||||
if err != nil {
|
||||
|
@ -7,12 +7,12 @@ import (
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
||||
//"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services"
|
||||
)
|
||||
|
||||
type Cron struct {
|
||||
Db *database.Queries
|
||||
//Db *database.Queries
|
||||
ctx context.Context
|
||||
timer *cron.Cron
|
||||
repo services.RepositoryService
|
||||
|
@ -8,7 +8,8 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
||||
//"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
||||
)
|
||||
|
||||
type discordField struct {
|
||||
@ -63,11 +64,11 @@ const (
|
||||
|
||||
type Discord struct {
|
||||
Subscriptions []string
|
||||
article database.Article
|
||||
article entity.ArticleEntity
|
||||
Message *DiscordMessage
|
||||
}
|
||||
|
||||
func NewDiscordWebHookMessage(Article database.Article) Discord {
|
||||
func NewDiscordWebHookMessage(Article entity.ArticleEntity) Discord {
|
||||
return Discord{
|
||||
article: Article,
|
||||
}
|
||||
|
@ -5,22 +5,19 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
||||
//"git.jamestombleson.com/jtom38/newsbot-api/internal/database"
|
||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/entity"
|
||||
"git.jamestombleson.com/jtom38/newsbot-api/internal/services/output"
|
||||
"github.com/google/uuid"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
var (
|
||||
article database.Article = database.Article{
|
||||
ID: uuid.New(),
|
||||
Sourceid: uuid.New(),
|
||||
article entity.ArticleEntity = entity.ArticleEntity{
|
||||
ID: 999,
|
||||
SourceID: 1,
|
||||
Tags: "unit, testing",
|
||||
Title: "Demo",
|
||||
Url: "https://github.com/jtom38/newsbot.collector.api",
|
||||
//Pubdate: time.Now(),
|
||||
Videoheight: 0,
|
||||
Videowidth: 0,
|
||||
Description: "Hello World",
|
||||
}
|
||||
blank string = ""
|
||||
|
Loading…
Reference in New Issue
Block a user