mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-07-04 17:06:49 +00:00
Oauth2 support
This commit is contained in:
@ -70,8 +70,6 @@ func NewToken() func(http.ResponseWriter, *http.Request) {
|
||||
switch payload.Type {
|
||||
case "ldap":
|
||||
newTokenLDAP(w, r, payload)
|
||||
case "oidc":
|
||||
newTokenOIDC(w, r, payload)
|
||||
case "local":
|
||||
newTokenLocal(w, r, payload)
|
||||
}
|
||||
@ -199,10 +197,6 @@ func newTokenLDAP(w http.ResponseWriter, r *http.Request, payload tokenPayload)
|
||||
}
|
||||
}
|
||||
|
||||
func newTokenOIDC(w http.ResponseWriter, r *http.Request, _ tokenPayload) {
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, "NOT YET SUPPORTED", nil)
|
||||
}
|
||||
|
||||
// RefreshToken an existing token by given them a new one with the same claims
|
||||
// Route: POST /auth/refresh
|
||||
func RefreshToken() func(http.ResponseWriter, *http.Request) {
|
||||
|
@ -21,11 +21,22 @@ func getPageInfoFromRequest(r *http.Request) (model.PageInfo, error) {
|
||||
return pageInfo, err
|
||||
}
|
||||
|
||||
// pageInfo.Sort = middleware.GetSortFromContext(r)
|
||||
|
||||
return pageInfo, nil
|
||||
}
|
||||
|
||||
func getQueryVarString(r *http.Request, varName string, required bool, defaultValue string) (string, error) {
|
||||
queryValues := r.URL.Query()
|
||||
varValue := queryValues.Get(varName)
|
||||
|
||||
if varValue == "" && required {
|
||||
return "", eris.Errorf("%v was not supplied in the request", varName)
|
||||
} else if varValue == "" {
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
return varValue, nil
|
||||
}
|
||||
|
||||
func getQueryVarInt(r *http.Request, varName string, required bool, defaultValue int) (int, error) {
|
||||
queryValues := r.URL.Query()
|
||||
varValue := queryValues.Get(varName)
|
||||
|
156
backend/internal/api/handler/oauth.go
Normal file
156
backend/internal/api/handler/oauth.go
Normal file
@ -0,0 +1,156 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
h "npm/internal/api/http"
|
||||
"npm/internal/entity/auth"
|
||||
"npm/internal/entity/setting"
|
||||
"npm/internal/entity/user"
|
||||
"npm/internal/errors"
|
||||
njwt "npm/internal/jwt"
|
||||
"npm/internal/logger"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// getRequestIPAddress will use X-FORWARDED-FOR header if it exists
|
||||
// otherwise it will use RemoteAddr
|
||||
func getRequestIPAddress(r *http.Request) string {
|
||||
// this Get is case insensitive
|
||||
xff := r.Header.Get("X-FORWARDED-FOR")
|
||||
if xff != "" {
|
||||
ip, _, _ := strings.Cut(xff, ",")
|
||||
return strings.TrimSpace(ip)
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
// OAuthLogin ...
|
||||
// Route: GET /oauth/login
|
||||
func OAuthLogin() func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !setting.AuthMethodEnabled(auth.TypeOAuth) {
|
||||
h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil)
|
||||
return
|
||||
}
|
||||
|
||||
redirectBase, _ := getQueryVarString(r, "redirect_base", false, "")
|
||||
url, err := auth.OAuthLogin(redirectBase, getRequestIPAddress(r))
|
||||
if err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
h.ResultResponseJSON(w, r, http.StatusOK, url)
|
||||
}
|
||||
}
|
||||
|
||||
// OAuthRedirect ...
|
||||
// Route: GET /oauth/redirect
|
||||
func OAuthRedirect() func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !setting.AuthMethodEnabled(auth.TypeOAuth) {
|
||||
h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil)
|
||||
return
|
||||
}
|
||||
|
||||
code, err := getQueryVarString(r, "code", true, "")
|
||||
if err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
ou, err := auth.OAuthReturn(r.Context(), code, getRequestIPAddress(r))
|
||||
if err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if ou.Identifier == "" {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, "User found, but OAuth identifier seems misconfigured", nil)
|
||||
return
|
||||
}
|
||||
|
||||
jwt, err := newTokenOAuth(ou)
|
||||
if err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// encode jwt to json
|
||||
j, _ := json.Marshal(jwt)
|
||||
|
||||
// Redirect to frontend with success
|
||||
http.Redirect(w, r, fmt.Sprintf("/?token_response=%s", url.QueryEscape(string(j))), http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
// newTokenOAuth takes a OAuthUser and creates a new token,
|
||||
// optionally creating a new user if one does not exist
|
||||
func newTokenOAuth(ou *auth.OAuthUser) (*njwt.GeneratedResponse, error) {
|
||||
// Get OAuth settings
|
||||
oAuthSettings, err := setting.GetOAuthSettings()
|
||||
if err != nil {
|
||||
logger.Error("OAuth settings not found", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get Auth by identity
|
||||
authObj, authErr := auth.GetByIdenityType(ou.GetID(), auth.TypeOAuth)
|
||||
if authErr == gorm.ErrRecordNotFound {
|
||||
// Auth is not found for this identity. We can create it
|
||||
if !oAuthSettings.AutoCreateUser {
|
||||
// user does not have an auth record
|
||||
// and auto create is disabled. Showing account disabled error
|
||||
// for the time being
|
||||
return nil, errors.ErrUserDisabled
|
||||
}
|
||||
|
||||
// Attempt to find user by email
|
||||
foundUser, err := user.GetByEmail(ou.GetEmail())
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// User not found, create user
|
||||
foundUser, err = user.CreateFromOAuthUser(ou)
|
||||
if err != nil {
|
||||
logger.Error("user.CreateFromOAuthUser", err)
|
||||
return nil, err
|
||||
}
|
||||
logger.Info("Created user from OAuth: %s, %s", ou.GetID(), foundUser.Email)
|
||||
} else if err != nil {
|
||||
logger.Error("user.GetByEmail", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create auth record and attach to this user
|
||||
authObj = auth.Model{
|
||||
UserID: foundUser.ID,
|
||||
Type: auth.TypeOAuth,
|
||||
Identity: ou.GetID(),
|
||||
}
|
||||
if err := authObj.Save(); err != nil {
|
||||
logger.Error("auth.Save", err)
|
||||
return nil, err
|
||||
}
|
||||
logger.Info("Created OAuth auth for user: %s, %s", ou.GetID(), foundUser.Email)
|
||||
} else if authErr != nil {
|
||||
logger.Error("auth.GetByIdenityType", err)
|
||||
return nil, authErr
|
||||
}
|
||||
|
||||
userObj, userErr := user.GetByID(authObj.UserID)
|
||||
if userErr != nil {
|
||||
return nil, userErr
|
||||
}
|
||||
|
||||
if userObj.IsDisabled {
|
||||
return nil, errors.ErrUserDisabled
|
||||
}
|
||||
|
||||
jwt, err := njwt.Generate(&userObj, false)
|
||||
return &jwt, err
|
||||
}
|
Reference in New Issue
Block a user