mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-23 01:20:11 +00:00
use Bearer auth instead of Basic
this was a relic from the python version, i'd modeled the auth code off some random blog post back then.
This commit is contained in:
parent
ba601935b5
commit
d64e98da37
36
api.go
36
api.go
@ -232,7 +232,7 @@ func (app *appContext) getOmbiUser(jfID string) (map[string]interface{}, int, er
|
|||||||
// @Param newUserDTO body newUserDTO true "New user request object"
|
// @Param newUserDTO body newUserDTO true "New user request object"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Router /users [post]
|
// @Router /users [post]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Users
|
// @tags Users
|
||||||
func (app *appContext) NewUserAdmin(gc *gin.Context) {
|
func (app *appContext) NewUserAdmin(gc *gin.Context) {
|
||||||
var req newUserDTO
|
var req newUserDTO
|
||||||
@ -411,7 +411,7 @@ func (app *appContext) NewUser(gc *gin.Context) {
|
|||||||
// @Failure 400 {object} stringResponse
|
// @Failure 400 {object} stringResponse
|
||||||
// @Failure 500 {object} errorListDTO "List of errors"
|
// @Failure 500 {object} errorListDTO "List of errors"
|
||||||
// @Router /users [delete]
|
// @Router /users [delete]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Users
|
// @tags Users
|
||||||
func (app *appContext) DeleteUser(gc *gin.Context) {
|
func (app *appContext) DeleteUser(gc *gin.Context) {
|
||||||
var req deleteUserDTO
|
var req deleteUserDTO
|
||||||
@ -475,7 +475,7 @@ func (app *appContext) DeleteUser(gc *gin.Context) {
|
|||||||
// @Param generateInviteDTO body generateInviteDTO true "New invite request object"
|
// @Param generateInviteDTO body generateInviteDTO true "New invite request object"
|
||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Router /invites [post]
|
// @Router /invites [post]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Invites
|
// @tags Invites
|
||||||
func (app *appContext) GenerateInvite(gc *gin.Context) {
|
func (app *appContext) GenerateInvite(gc *gin.Context) {
|
||||||
var req generateInviteDTO
|
var req generateInviteDTO
|
||||||
@ -538,7 +538,7 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
|||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Failure 500 {object} stringResponse
|
// @Failure 500 {object} stringResponse
|
||||||
// @Router /invites/profile [post]
|
// @Router /invites/profile [post]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Profiles & Settings
|
// @tags Profiles & Settings
|
||||||
func (app *appContext) SetProfile(gc *gin.Context) {
|
func (app *appContext) SetProfile(gc *gin.Context) {
|
||||||
var req inviteProfileDTO
|
var req inviteProfileDTO
|
||||||
@ -561,7 +561,7 @@ func (app *appContext) SetProfile(gc *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} getProfilesDTO
|
// @Success 200 {object} getProfilesDTO
|
||||||
// @Router /profiles [get]
|
// @Router /profiles [get]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Profiles & Settings
|
// @tags Profiles & Settings
|
||||||
func (app *appContext) GetProfiles(gc *gin.Context) {
|
func (app *appContext) GetProfiles(gc *gin.Context) {
|
||||||
app.storage.loadProfiles()
|
app.storage.loadProfiles()
|
||||||
@ -586,7 +586,7 @@ func (app *appContext) GetProfiles(gc *gin.Context) {
|
|||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Failure 500 {object} stringResponse
|
// @Failure 500 {object} stringResponse
|
||||||
// @Router /profiles/default [post]
|
// @Router /profiles/default [post]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Profiles & Settings
|
// @tags Profiles & Settings
|
||||||
func (app *appContext) SetDefaultProfile(gc *gin.Context) {
|
func (app *appContext) SetDefaultProfile(gc *gin.Context) {
|
||||||
req := profileChangeDTO{}
|
req := profileChangeDTO{}
|
||||||
@ -615,7 +615,7 @@ func (app *appContext) SetDefaultProfile(gc *gin.Context) {
|
|||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Failure 500 {object} stringResponse
|
// @Failure 500 {object} stringResponse
|
||||||
// @Router /profiles [post]
|
// @Router /profiles [post]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Profiles & Settings
|
// @tags Profiles & Settings
|
||||||
func (app *appContext) CreateProfile(gc *gin.Context) {
|
func (app *appContext) CreateProfile(gc *gin.Context) {
|
||||||
app.info.Println("Profile creation requested")
|
app.info.Println("Profile creation requested")
|
||||||
@ -655,7 +655,7 @@ func (app *appContext) CreateProfile(gc *gin.Context) {
|
|||||||
// @Param profileChangeDTO body profileChangeDTO true "Delete profile object"
|
// @Param profileChangeDTO body profileChangeDTO true "Delete profile object"
|
||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Router /profiles [delete]
|
// @Router /profiles [delete]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Profiles & Settings
|
// @tags Profiles & Settings
|
||||||
func (app *appContext) DeleteProfile(gc *gin.Context) {
|
func (app *appContext) DeleteProfile(gc *gin.Context) {
|
||||||
req := profileChangeDTO{}
|
req := profileChangeDTO{}
|
||||||
@ -672,7 +672,7 @@ func (app *appContext) DeleteProfile(gc *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} getInvitesDTO
|
// @Success 200 {object} getInvitesDTO
|
||||||
// @Router /invites [get]
|
// @Router /invites [get]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Invites
|
// @tags Invites
|
||||||
func (app *appContext) GetInvites(gc *gin.Context) {
|
func (app *appContext) GetInvites(gc *gin.Context) {
|
||||||
app.debug.Println("Invites requested")
|
app.debug.Println("Invites requested")
|
||||||
@ -749,7 +749,7 @@ func (app *appContext) GetInvites(gc *gin.Context) {
|
|||||||
// @Failure 400 {object} stringResponse
|
// @Failure 400 {object} stringResponse
|
||||||
// @Failure 500 {object} stringResponse
|
// @Failure 500 {object} stringResponse
|
||||||
// @Router /invites/notify [post]
|
// @Router /invites/notify [post]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Other
|
// @tags Other
|
||||||
func (app *appContext) SetNotify(gc *gin.Context) {
|
func (app *appContext) SetNotify(gc *gin.Context) {
|
||||||
var req map[string]map[string]bool
|
var req map[string]map[string]bool
|
||||||
@ -811,7 +811,7 @@ func (app *appContext) SetNotify(gc *gin.Context) {
|
|||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Failure 400 {object} stringResponse
|
// @Failure 400 {object} stringResponse
|
||||||
// @Router /invites [delete]
|
// @Router /invites [delete]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Invites
|
// @tags Invites
|
||||||
func (app *appContext) DeleteInvite(gc *gin.Context) {
|
func (app *appContext) DeleteInvite(gc *gin.Context) {
|
||||||
var req deleteInviteDTO
|
var req deleteInviteDTO
|
||||||
@ -847,7 +847,7 @@ func parseDt(date string) time.Time {
|
|||||||
// @Success 200 {object} getUsersDTO
|
// @Success 200 {object} getUsersDTO
|
||||||
// @Failure 500 {object} stringResponse
|
// @Failure 500 {object} stringResponse
|
||||||
// @Router /users [get]
|
// @Router /users [get]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Users
|
// @tags Users
|
||||||
func (app *appContext) GetUsers(gc *gin.Context) {
|
func (app *appContext) GetUsers(gc *gin.Context) {
|
||||||
app.debug.Println("Users requested")
|
app.debug.Println("Users requested")
|
||||||
@ -884,7 +884,7 @@ func (app *appContext) GetUsers(gc *gin.Context) {
|
|||||||
// @Success 200 {object} ombiUsersDTO
|
// @Success 200 {object} ombiUsersDTO
|
||||||
// @Failure 500 {object} stringResponse
|
// @Failure 500 {object} stringResponse
|
||||||
// @Router /ombi/users [get]
|
// @Router /ombi/users [get]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Ombi
|
// @tags Ombi
|
||||||
func (app *appContext) OmbiUsers(gc *gin.Context) {
|
func (app *appContext) OmbiUsers(gc *gin.Context) {
|
||||||
app.debug.Println("Ombi users requested")
|
app.debug.Println("Ombi users requested")
|
||||||
@ -911,7 +911,7 @@ func (app *appContext) OmbiUsers(gc *gin.Context) {
|
|||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Failure 500 {object} stringResponse
|
// @Failure 500 {object} stringResponse
|
||||||
// @Router /ombi/defaults [post]
|
// @Router /ombi/defaults [post]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Ombi
|
// @tags Ombi
|
||||||
func (app *appContext) SetOmbiDefaults(gc *gin.Context) {
|
func (app *appContext) SetOmbiDefaults(gc *gin.Context) {
|
||||||
var req ombiUser
|
var req ombiUser
|
||||||
@ -933,7 +933,7 @@ func (app *appContext) SetOmbiDefaults(gc *gin.Context) {
|
|||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Failure 500 {object} stringResponse
|
// @Failure 500 {object} stringResponse
|
||||||
// @Router /users/emails [post]
|
// @Router /users/emails [post]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Users
|
// @tags Users
|
||||||
func (app *appContext) ModifyEmails(gc *gin.Context) {
|
func (app *appContext) ModifyEmails(gc *gin.Context) {
|
||||||
var req modifyEmailsDTO
|
var req modifyEmailsDTO
|
||||||
@ -974,7 +974,7 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
|
|||||||
// @Success 200 {object} errorListDTO
|
// @Success 200 {object} errorListDTO
|
||||||
// @Failure 500 {object} errorListDTO "Lists of errors that occured while applying settings"
|
// @Failure 500 {object} errorListDTO "Lists of errors that occured while applying settings"
|
||||||
// @Router /users/settings [post]
|
// @Router /users/settings [post]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Profiles & Settings
|
// @tags Profiles & Settings
|
||||||
func (app *appContext) ApplySettings(gc *gin.Context) {
|
func (app *appContext) ApplySettings(gc *gin.Context) {
|
||||||
app.info.Println("User settings change requested")
|
app.info.Println("User settings change requested")
|
||||||
@ -1058,7 +1058,7 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} configDTO "Uses the same format as config-base.json"
|
// @Success 200 {object} configDTO "Uses the same format as config-base.json"
|
||||||
// @Router /config [get]
|
// @Router /config [get]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Configuration
|
// @tags Configuration
|
||||||
func (app *appContext) GetConfig(gc *gin.Context) {
|
func (app *appContext) GetConfig(gc *gin.Context) {
|
||||||
app.info.Println("Config requested")
|
app.info.Println("Config requested")
|
||||||
@ -1118,7 +1118,7 @@ func (app *appContext) GetConfig(gc *gin.Context) {
|
|||||||
// @Param appConfig body configDTO true "Config split into sections as in config.ini, all values as strings."
|
// @Param appConfig body configDTO true "Config split into sections as in config.ini, all values as strings."
|
||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Router /config [post]
|
// @Router /config [post]
|
||||||
// @Security ApiKeyBlankPassword
|
// @Security Bearer
|
||||||
// @tags Configuration
|
// @tags Configuration
|
||||||
func (app *appContext) ModifyConfig(gc *gin.Context) {
|
func (app *appContext) ModifyConfig(gc *gin.Context) {
|
||||||
app.info.Println("Config modification requested")
|
app.info.Println("Config modification requested")
|
||||||
|
239
auth.go
239
auth.go
@ -46,14 +46,13 @@ func CreateToken(userId, jfId string) (string, string, error) {
|
|||||||
// Check header for token
|
// Check header for token
|
||||||
func (app *appContext) authenticate(gc *gin.Context) {
|
func (app *appContext) authenticate(gc *gin.Context) {
|
||||||
header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2)
|
header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2)
|
||||||
if header[0] != "Basic" {
|
if header[0] != "Bearer" {
|
||||||
app.debug.Println("Invalid authentication header")
|
app.debug.Println("Invalid authorization header")
|
||||||
respond(401, "Unauthorized", gc)
|
respond(401, "Unauthorized", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
auth, _ := base64.StdEncoding.DecodeString(header[1])
|
creds, _ := base64.StdEncoding.DecodeString(header[1])
|
||||||
creds := strings.SplitN(string(auth), ":", 2)
|
token, err := jwt.Parse(string(creds), checkToken)
|
||||||
token, err := jwt.Parse(creds[0], checkToken)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.debug.Printf("Auth denied: %s", err)
|
app.debug.Printf("Auth denied: %s", err)
|
||||||
respond(401, "Unauthorized", gc)
|
respond(401, "Unauthorized", gc)
|
||||||
@ -103,146 +102,128 @@ type getTokenDTO struct {
|
|||||||
Token string `json:"token" example:"kjsdklsfdkljfsjsdfklsdfkldsfjdfskjsdfjklsdf"` // API token for use with everything else.
|
Token string `json:"token" example:"kjsdklsfdkljfsjsdfklsdfkldsfjdfskjsdfjklsdf"` // API token for use with everything else.
|
||||||
}
|
}
|
||||||
|
|
||||||
// getToken checks the header for a username and password, as well as checking the refresh cookie.
|
// @Summary Grabs an API token using username & password.
|
||||||
|
// @description Click the lock icon next to this, login with your normal jfa-go credentials. Click 'try it out', then 'execute' and an API Key will be returned, copy it (not including quotes). On any of the other routes, click the lock icon and set the API key as "Bearer <your api key>".
|
||||||
// @Summary Grabs an API token using username & password, or via a refresh cookie.
|
|
||||||
// @description Click the lock icon next to this, login with your normal jfa-go credentials. Click 'try it out', then 'execute' and an API Key will be returned, copy it (not including quotes). On any of the other routes, click the lock icon and use the token as your -Username-. The password can be anything.
|
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} getTokenDTO
|
// @Success 200 {object} getTokenDTO
|
||||||
// @Failure 401 {object} stringResponse
|
// @Failure 401 {object} stringResponse
|
||||||
// @Router /getToken [get]
|
// @Router /token/login [get]
|
||||||
// @tags Auth
|
// @tags Auth
|
||||||
// @Security getTokenAuth
|
// @Security getTokenAuth
|
||||||
func (app *appContext) getToken(gc *gin.Context) {
|
func (app *appContext) getTokenLogin(gc *gin.Context) {
|
||||||
app.info.Println("Token requested (login attempt)")
|
app.info.Println("Token requested (login attempt)")
|
||||||
header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2)
|
header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2)
|
||||||
auth, _ := base64.StdEncoding.DecodeString(header[1])
|
auth, _ := base64.StdEncoding.DecodeString(header[1])
|
||||||
creds := strings.SplitN(string(auth), ":", 2)
|
creds := strings.SplitN(string(auth), ":", 2)
|
||||||
// check cookie first
|
|
||||||
var userID, jfID string
|
var userID, jfID string
|
||||||
valid := false
|
if creds[0] == "" || creds[1] == "" {
|
||||||
noLogin := false
|
app.debug.Println("Auth denied: blank username/password")
|
||||||
checkLogin := func() {
|
respond(401, "Unauthorized", gc)
|
||||||
if creds[0] == "" || creds[1] == "" {
|
return
|
||||||
app.debug.Println("Auth denied: blank username/password")
|
}
|
||||||
respond(401, "Unauthorized", gc)
|
match := false
|
||||||
|
for _, user := range app.users {
|
||||||
|
if user.Username == creds[0] && user.Password == creds[1] {
|
||||||
|
match = true
|
||||||
|
app.debug.Println("Found existing user")
|
||||||
|
userID = user.UserID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !app.jellyfinLogin && !match {
|
||||||
|
app.info.Println("Auth denied: Invalid username/password")
|
||||||
|
respond(401, "Unauthorized", gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
var status int
|
||||||
|
var err error
|
||||||
|
var user map[string]interface{}
|
||||||
|
user, status, err = app.authJf.Authenticate(creds[0], creds[1])
|
||||||
|
if status != 200 || err != nil {
|
||||||
|
if status == 401 || status == 400 {
|
||||||
|
app.info.Println("Auth denied: Invalid username/password (Jellyfin)")
|
||||||
|
respond(401, "Unauthorized", gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
app.err.Printf("Auth failed: Couldn't authenticate with Jellyfin (%d/%s)", status, err)
|
||||||
|
respond(500, "Jellyfin error", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
match := false
|
jfID = user["Id"].(string)
|
||||||
for _, user := range app.users {
|
if app.config.Section("ui").Key("admin_only").MustBool(true) {
|
||||||
if user.Username == creds[0] && user.Password == creds[1] {
|
if !user["Policy"].(map[string]interface{})["IsAdministrator"].(bool) {
|
||||||
match = true
|
app.debug.Printf("Auth denied: Users \"%s\" isn't admin", creds[0])
|
||||||
app.debug.Println("Found existing user")
|
respond(401, "Unauthorized", gc)
|
||||||
userID = user.UserID
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !app.jellyfinLogin && !match {
|
|
||||||
app.info.Println("Auth denied: Invalid username/password")
|
|
||||||
respond(401, "Unauthorized", gc)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
var status int
|
|
||||||
var err error
|
|
||||||
var user map[string]interface{}
|
|
||||||
user, status, err = app.authJf.Authenticate(creds[0], creds[1])
|
|
||||||
if status != 200 || err != nil {
|
|
||||||
if status == 401 || status == 400 {
|
|
||||||
app.info.Println("Auth denied: Invalid username/password (Jellyfin)")
|
|
||||||
respond(401, "Unauthorized", gc)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
app.err.Printf("Auth failed: Couldn't authenticate with Jellyfin (%d/%s)", status, err)
|
|
||||||
respond(500, "Jellyfin error", gc)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jfID = user["Id"].(string)
|
|
||||||
if app.config.Section("ui").Key("admin_only").MustBool(true) {
|
|
||||||
if !user["Policy"].(map[string]interface{})["IsAdministrator"].(bool) {
|
|
||||||
app.debug.Printf("Auth denied: Users \"%s\" isn't admin", creds[0])
|
|
||||||
respond(401, "Unauthorized", gc)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// New users are only added when using jellyfinLogin.
|
|
||||||
userID = shortuuid.New()
|
|
||||||
newUser := User{
|
|
||||||
UserID: userID,
|
|
||||||
}
|
|
||||||
app.debug.Printf("Token generated for user \"%s\"", creds[0])
|
|
||||||
app.users = append(app.users, newUser)
|
|
||||||
}
|
}
|
||||||
valid = true
|
// New users are only added when using jellyfinLogin.
|
||||||
}
|
userID = shortuuid.New()
|
||||||
checkCookie := func() {
|
newUser := User{
|
||||||
cookie, err := gc.Cookie("refresh")
|
UserID: userID,
|
||||||
if err == nil && cookie != "" {
|
|
||||||
for _, token := range app.invalidTokens {
|
|
||||||
if cookie == token {
|
|
||||||
if creds[0] == "" || creds[1] == "" {
|
|
||||||
app.debug.Println("getToken denied: Invalid refresh token and no username/password provided")
|
|
||||||
respond(401, "Unauthorized", gc)
|
|
||||||
noLogin = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
app.debug.Println("getToken: Invalid token but username/password provided")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
token, err := jwt.Parse(cookie, checkToken)
|
|
||||||
if err != nil {
|
|
||||||
if creds[0] == "" || creds[1] == "" {
|
|
||||||
app.debug.Println("getToken denied: Invalid refresh token and no username/password provided")
|
|
||||||
respond(401, "Unauthorized", gc)
|
|
||||||
noLogin = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
app.debug.Println("getToken: Invalid token but username/password provided")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
claims, ok := token.Claims.(jwt.MapClaims)
|
|
||||||
expiryUnix, err := strconv.ParseInt(claims["exp"].(string), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
if creds[0] == "" || creds[1] == "" {
|
|
||||||
app.debug.Printf("getToken denied: Invalid token (%s) and no username/password provided", err)
|
|
||||||
respond(401, "Unauthorized", gc)
|
|
||||||
noLogin = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
app.debug.Printf("getToken: Invalid token (%s) but username/password provided", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
expiry := time.Unix(expiryUnix, 0)
|
|
||||||
if !(ok && token.Valid && claims["type"].(string) == "refresh" && expiry.After(time.Now())) {
|
|
||||||
if creds[0] == "" || creds[1] == "" {
|
|
||||||
app.debug.Printf("getToken denied: Invalid token (%s) and no username/password provided", err)
|
|
||||||
respond(401, "Unauthorized", gc)
|
|
||||||
noLogin = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
app.debug.Printf("getToken: Invalid token (%s) but username/password provided", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userID = claims["id"].(string)
|
|
||||||
jfID = claims["jfid"].(string)
|
|
||||||
valid = true
|
|
||||||
}
|
}
|
||||||
|
app.debug.Printf("Token generated for user \"%s\"", creds[0])
|
||||||
|
app.users = append(app.users, newUser)
|
||||||
}
|
}
|
||||||
checkCookie()
|
token, refresh, err := CreateToken(userID, jfID)
|
||||||
if !valid && !noLogin {
|
if err != nil {
|
||||||
checkLogin()
|
app.err.Printf("getToken failed: Couldn't generate token (%s)", err)
|
||||||
}
|
respond(500, "Couldn't generate token", gc)
|
||||||
if valid {
|
return
|
||||||
token, refresh, err := CreateToken(userID, jfID)
|
|
||||||
if err != nil {
|
|
||||||
app.err.Printf("getToken failed: Couldn't generate token (%s)", err)
|
|
||||||
respond(500, "Couldn't generate token", gc)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gc.SetCookie("refresh", refresh, (3600 * 24), "/", gc.Request.URL.Hostname(), true, true)
|
|
||||||
gc.JSON(200, getTokenDTO{token})
|
|
||||||
} else {
|
|
||||||
gc.AbortWithStatus(401)
|
|
||||||
}
|
}
|
||||||
|
gc.SetCookie("refresh", refresh, (3600 * 24), "/", gc.Request.URL.Hostname(), true, true)
|
||||||
|
gc.JSON(200, getTokenDTO{token})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Grabs an API token using a refresh token from cookies.
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} getTokenDTO
|
||||||
|
// @Failure 401 {object} stringResponse
|
||||||
|
// @Router /token/refresh [get]
|
||||||
|
// @tags Auth
|
||||||
|
func (app *appContext) getTokenRefresh(gc *gin.Context) {
|
||||||
|
app.debug.Println("Token requested (refresh token)")
|
||||||
|
cookie, err := gc.Cookie("refresh")
|
||||||
|
if err != nil || cookie == "" {
|
||||||
|
app.debug.Printf("getTokenRefresh denied: Couldn't get token: %s", err)
|
||||||
|
respond(400, "Couldn't get token", gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, token := range app.invalidTokens {
|
||||||
|
if cookie == token {
|
||||||
|
app.debug.Println("getTokenRefresh: Invalid token")
|
||||||
|
respond(401, "Invalid token", gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
token, err := jwt.Parse(cookie, checkToken)
|
||||||
|
if err != nil {
|
||||||
|
app.debug.Println("getTokenRefresh: Invalid token")
|
||||||
|
respond(400, "Invalid token", gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
|
expiryUnix, err := strconv.ParseInt(claims["exp"].(string), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
app.debug.Printf("getTokenRefresh: Invalid token expiry: %s", err)
|
||||||
|
respond(401, "Invalid token", gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expiry := time.Unix(expiryUnix, 0)
|
||||||
|
if !(ok && token.Valid && claims["type"].(string) == "refresh" && expiry.After(time.Now())) {
|
||||||
|
app.debug.Printf("getTokenRefresh: Invalid token: %s", err)
|
||||||
|
respond(401, "Invalid token", gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID := claims["id"].(string)
|
||||||
|
jfID := claims["jfid"].(string)
|
||||||
|
jwt, refresh, err := CreateToken(userID, jfID)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("getTokenRefresh failed: Couldn't generate token (%s)", err)
|
||||||
|
respond(500, "Couldn't generate token", gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gc.SetCookie("refresh", refresh, (3600 * 24), "/", gc.Request.URL.Hostname(), true, true)
|
||||||
|
gc.JSON(200, getTokenDTO{jwt})
|
||||||
}
|
}
|
||||||
|
@ -617,7 +617,7 @@
|
|||||||
"description": "Deprecated in favor of User Profiles. Location of stored user policy template (json)."
|
"description": "Deprecated in favor of User Profiles. Location of stored user policy template (json)."
|
||||||
},
|
},
|
||||||
"user_configuration": {
|
"user_configuration": {
|
||||||
"name": "userConfiguration (Deprecated in favor of User Profiles.",
|
"name": "userConfiguration (Deprecated)",
|
||||||
"required": false,
|
"required": false,
|
||||||
"requires_restart": true,
|
"requires_restart": true,
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@ -625,7 +625,7 @@
|
|||||||
"description": "Deprecated in favor of User Profiles. Location of stored user configuration template (used for setting homescreen layout) (json)"
|
"description": "Deprecated in favor of User Profiles. Location of stored user configuration template (used for setting homescreen layout) (json)"
|
||||||
},
|
},
|
||||||
"user_displayprefs": {
|
"user_displayprefs": {
|
||||||
"name": "displayPreferences (Deprecated in favor of User Profiles.",
|
"name": "displayPreferences (Deprecated)",
|
||||||
"required": false,
|
"required": false,
|
||||||
"requires_restart": true,
|
"requires_restart": true,
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
8
main.go
8
main.go
@ -511,7 +511,8 @@ func start(asDaemon, firstCall bool) {
|
|||||||
}
|
}
|
||||||
if !firstRun {
|
if !firstRun {
|
||||||
router.GET("/", app.AdminPage)
|
router.GET("/", app.AdminPage)
|
||||||
router.GET("/getToken", app.getToken)
|
router.GET("/token/login", app.getTokenLogin)
|
||||||
|
router.GET("/token/refresh", app.getTokenRefresh)
|
||||||
router.POST("/newUser", app.NewUser)
|
router.POST("/newUser", app.NewUser)
|
||||||
router.Use(static.Serve("/invite/", static.LocalFile(filepath.Join(app.local_path, "static"), false)))
|
router.Use(static.Serve("/invite/", static.LocalFile(filepath.Join(app.local_path, "static"), false)))
|
||||||
router.GET("/invite/:invCode", app.InviteProxy)
|
router.GET("/invite/:invCode", app.InviteProxy)
|
||||||
@ -607,8 +608,9 @@ func flagPassed(name string) (found bool) {
|
|||||||
// @license.url https://raw.githubusercontent.com/hrfee/jfa-go/main/LICENSE
|
// @license.url https://raw.githubusercontent.com/hrfee/jfa-go/main/LICENSE
|
||||||
// @BasePath /
|
// @BasePath /
|
||||||
|
|
||||||
// @securityDefinitions.basic ApiKeyBlankPassword
|
// @securityDefinitions.apikey Bearer
|
||||||
// @name ApiKeyBlankPassword
|
// @in header
|
||||||
|
// @name Authorization
|
||||||
|
|
||||||
// @securityDefinitions.basic getTokenAuth
|
// @securityDefinitions.basic getTokenAuth
|
||||||
// @name getTokenAuth
|
// @name getTokenAuth
|
||||||
|
11
ts/admin.ts
11
ts/admin.ts
@ -121,8 +121,15 @@ window.toClipboard = (str: string): void => {
|
|||||||
function login(username: string, password: string, modal: boolean, button?: HTMLButtonElement, run?: (arg0: number) => void): void {
|
function login(username: string, password: string, modal: boolean, button?: HTMLButtonElement, run?: (arg0: number) => void): void {
|
||||||
const req = new XMLHttpRequest();
|
const req = new XMLHttpRequest();
|
||||||
req.responseType = 'json';
|
req.responseType = 'json';
|
||||||
req.open("GET", "/getToken", true);
|
let url = "/token/login";
|
||||||
req.setRequestHeader("Authorization", "Basic " + btoa(username + ":" + password));
|
const refresh = (username == "" && password == "");
|
||||||
|
if (refresh) {
|
||||||
|
url = "/token/refresh";
|
||||||
|
}
|
||||||
|
req.open("GET", url, true);
|
||||||
|
if (!refresh) {
|
||||||
|
req.setRequestHeader("Authorization", "Basic " + btoa(username + ":" + password));
|
||||||
|
}
|
||||||
req.onreadystatechange = function (): void {
|
req.onreadystatechange = function (): void {
|
||||||
if (this.readyState == 4) {
|
if (this.readyState == 4) {
|
||||||
if (this.status != 200) {
|
if (this.status != 200) {
|
||||||
|
@ -48,7 +48,7 @@ export const _get = (url: string, data: Object, onreadystatechange: () => void):
|
|||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("GET", url, true);
|
req.open("GET", url, true);
|
||||||
req.responseType = 'json';
|
req.responseType = 'json';
|
||||||
req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":"));
|
req.setRequestHeader("Authorization", "Bearer " + btoa(window.token));
|
||||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||||
req.onreadystatechange = onreadystatechange;
|
req.onreadystatechange = onreadystatechange;
|
||||||
req.send(JSON.stringify(data));
|
req.send(JSON.stringify(data));
|
||||||
@ -60,7 +60,7 @@ export const _post = (url: string, data: Object, onreadystatechange: () => void,
|
|||||||
if (response) {
|
if (response) {
|
||||||
req.responseType = 'json';
|
req.responseType = 'json';
|
||||||
}
|
}
|
||||||
req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":"));
|
req.setRequestHeader("Authorization", "Bearer " + btoa(window.token));
|
||||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||||
req.onreadystatechange = onreadystatechange;
|
req.onreadystatechange = onreadystatechange;
|
||||||
req.send(JSON.stringify(data));
|
req.send(JSON.stringify(data));
|
||||||
@ -69,7 +69,7 @@ export const _post = (url: string, data: Object, onreadystatechange: () => void,
|
|||||||
export function _delete(url: string, data: Object, onreadystatechange: () => void): void {
|
export function _delete(url: string, data: Object, onreadystatechange: () => void): void {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("DELETE", url, true);
|
req.open("DELETE", url, true);
|
||||||
req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":"));
|
req.setRequestHeader("Authorization", "Bearer " + btoa(window.token));
|
||||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||||
req.onreadystatechange = onreadystatechange;
|
req.onreadystatechange = onreadystatechange;
|
||||||
req.send(JSON.stringify(data));
|
req.send(JSON.stringify(data));
|
||||||
|
Loading…
Reference in New Issue
Block a user