mirror of
https://git.rwth-aachen.de/acs/public/villas/web-backend-go/
synced 2025-03-30 00:00:12 +01:00
final fixes for external authentication
This commit is contained in:
parent
914db083ed
commit
b24387a22a
3 changed files with 96 additions and 80 deletions
|
@ -39,56 +39,70 @@ func InitConfig() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dbHost = flag.String("db-host", "/var/run/postgresql", "Host of the PostgreSQL database (default is /var/run/postgresql for localhost DB on Ubuntu systems)")
|
dbHost = flag.String("db-host", "/var/run/postgresql", "Host of the PostgreSQL database (default is /var/run/postgresql for localhost DB on Ubuntu systems)")
|
||||||
dbName = flag.String("db-name", "villasdb", "Name of the database to use (default is villasdb)")
|
dbName = flag.String("db-name", "villasdb", "Name of the database to use (default is villasdb)")
|
||||||
dbUser = flag.String("db-user", "", "Username of database connection (default is <empty>)")
|
dbUser = flag.String("db-user", "", "Username of database connection (default is <empty>)")
|
||||||
dbPass = flag.String("db-pass", "", "Password of database connection (default is <empty>)")
|
dbPass = flag.String("db-pass", "", "Password of database connection (default is <empty>)")
|
||||||
dbSSLMode = flag.String("db-ssl-mode", "disable", "SSL mode of DB (default is disable)") // TODO: change default for production
|
dbSSLMode = flag.String("db-ssl-mode", "disable", "SSL mode of DB (default is disable)") // TODO: change default for production
|
||||||
amqpHost = flag.String("amqp-host", "", "If set, use this as host for AMQP broker (default is disabled)")
|
amqpHost = flag.String("amqp-host", "", "If set, use this as host for AMQP broker (default is disabled)")
|
||||||
amqpUser = flag.String("amqp-user", "", "Username for AMQP broker")
|
amqpUser = flag.String("amqp-user", "", "Username for AMQP broker")
|
||||||
amqpPass = flag.String("amqp-pass", "", "Password for AMQP broker")
|
amqpPass = flag.String("amqp-pass", "", "Password for AMQP broker")
|
||||||
configFile = flag.String("config", "", "Path to YAML configuration file")
|
configFile = flag.String("config", "", "Path to YAML configuration file")
|
||||||
mode = flag.String("mode", "release", "Select debug/release/test mode (default is release)")
|
mode = flag.String("mode", "release", "Select debug/release/test mode (default is release)")
|
||||||
port = flag.String("port", "4000", "Port of the backend (default is 4000)")
|
port = flag.String("port", "4000", "Port of the backend (default is 4000)")
|
||||||
baseHost = flag.String("base-host", "localhost:4000", "The host at which the backend is hosted (default: localhost)")
|
baseHost = flag.String("base-host", "localhost:4000", "The host at which the backend is hosted (default: localhost)")
|
||||||
basePath = flag.String("base-path", "/api/v2", "The path at which the API routes are located (default /api/v2)")
|
basePath = flag.String("base-path", "/api/v2", "The path at which the API routes are located (default /api/v2)")
|
||||||
adminUser = flag.String("admin-user", "", "Initial admin username")
|
adminUser = flag.String("admin-user", "", "Initial admin username")
|
||||||
adminPass = flag.String("admin-pass", "", "Initial admin password")
|
adminPass = flag.String("admin-pass", "", "Initial admin password")
|
||||||
adminMail = flag.String("admin-mail", "", "Initial admin mail address")
|
adminMail = flag.String("admin-mail", "", "Initial admin mail address")
|
||||||
s3Bucket = flag.String("s3-bucket", "", "S3 Bucket for uploading files")
|
s3Bucket = flag.String("s3-bucket", "", "S3 Bucket for uploading files")
|
||||||
s3Endpoint = flag.String("s3-endpoint", "", "Endpoint of S3 API for file uploads")
|
s3Endpoint = flag.String("s3-endpoint", "", "Endpoint of S3 API for file uploads")
|
||||||
s3Region = flag.String("s3-region", "default", "S3 Region for file uploads")
|
s3Region = flag.String("s3-region", "default", "S3 Region for file uploads")
|
||||||
s3NoSSL = flag.Bool("s3-nossl", false, "Use encrypted connections to the S3 API")
|
s3NoSSL = flag.Bool("s3-nossl", false, "Use encrypted connections to the S3 API")
|
||||||
s3PathStyle = flag.Bool("s3-pathstyle", false, "Use path-style S3 API")
|
s3PathStyle = flag.Bool("s3-pathstyle", false, "Use path-style S3 API")
|
||||||
jwtSecret = flag.String("jwt-secret", "This should NOT be here!!@33$8&", "The JSON Web Token secret")
|
jwtSecret = flag.String("jwt-secret", "This should NOT be here!!@33$8&", "The JSON Web Token secret")
|
||||||
jwtExpiresAfter = flag.String("jwt-expires-after", "168h" /* 1 week */, "The time after which the JSON Web Token expires")
|
jwtExpiresAfter = flag.String("jwt-expires-after", "168h" /* 1 week */, "The time after which the JSON Web Token expires")
|
||||||
testDataPath = flag.String("test-data-path", "database/testdata.json", "The path to the test data json file (used in test mode)")
|
authExternal = flag.Bool("auth-external", false, "Use external authentication via X-Forwarded-User header (e.g. OAuth2 Proxy)")
|
||||||
authExternal = flag.Bool("auth.external", false, "Use external authentication via X-Forwarded-User header (e.g. OAuth2 Proxy)")
|
authExternalAuthorizeURL = flag.String("authexternal-authorize-url", "/oauth2/start", "A URL to initiate external login procedure")
|
||||||
|
authExternalProviderName = flag.String("auth-external-provider-name", "JupyterHub", "A name of the external authentication provider")
|
||||||
|
authLogoutURL = flag.String("auth-logout-url", "/oauth2/sign_out?rd=https%3A%2F%2Fjupyter.k8s.eonerc.rwth-aachen.de%2Fhub%2Flogout", "The URL to redirect the user to log out")
|
||||||
|
title = flag.String("title", "VILLASweb", "Title shown in the frontend")
|
||||||
|
subTitle = flag.String("sub-title", "", "Sub-title shown in the frontend")
|
||||||
|
contactName = flag.String("contact-name", "Steffen Vogel", "Name of the administrative contact")
|
||||||
|
contactMail = flag.String("contact-mail", "svogel2@eonerc.rwth-aachen.de", "EMail of the administrative contact")
|
||||||
|
testDataPath = flag.String("test-data-path", "database/testdata.json", "The path to the test data json file (used in test mode)")
|
||||||
)
|
)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
static := map[string]string{
|
static := map[string]string{
|
||||||
"db.host": *dbHost,
|
"db.host": *dbHost,
|
||||||
"db.name": *dbName,
|
"db.name": *dbName,
|
||||||
"db.user": *dbUser,
|
"db.user": *dbUser,
|
||||||
"db.pass": *dbPass,
|
"db.pass": *dbPass,
|
||||||
"db.ssl": *dbSSLMode,
|
"db.ssl": *dbSSLMode,
|
||||||
"amqp.host": *amqpHost,
|
"amqp.host": *amqpHost,
|
||||||
"amqp.user": *amqpUser,
|
"amqp.user": *amqpUser,
|
||||||
"amqp.pass": *amqpPass,
|
"amqp.pass": *amqpPass,
|
||||||
"mode": *mode,
|
"mode": *mode,
|
||||||
"port": *port,
|
"port": *port,
|
||||||
"base.host": *baseHost,
|
"base.host": *baseHost,
|
||||||
"base.path": *basePath,
|
"base.path": *basePath,
|
||||||
"admin.user": *adminUser,
|
"admin.user": *adminUser,
|
||||||
"admin.pass": *adminPass,
|
"admin.pass": *adminPass,
|
||||||
"admin.mail": *adminMail,
|
"admin.mail": *adminMail,
|
||||||
"s3.bucket": *s3Bucket,
|
"s3.bucket": *s3Bucket,
|
||||||
"s3.endpoint": *s3Endpoint,
|
"s3.endpoint": *s3Endpoint,
|
||||||
"s3.region": *s3Region,
|
"s3.region": *s3Region,
|
||||||
"jwt.secret": *jwtSecret,
|
"jwt.secret": *jwtSecret,
|
||||||
"jwt.expires-after": *jwtExpiresAfter,
|
"jwt.expires-after": *jwtExpiresAfter,
|
||||||
"test.datapath": *testDataPath,
|
"auth.external.authorize-url": *authExternalAuthorizeURL,
|
||||||
|
"auth.external.provider-name": *authExternalProviderName,
|
||||||
|
"auth.logout-url": *authLogoutURL,
|
||||||
|
"title": *title,
|
||||||
|
"sub-title": *subTitle,
|
||||||
|
"contact.name": *contactName,
|
||||||
|
"contact.mail": *contactMail,
|
||||||
|
"test.datapath": *testDataPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
if *s3NoSSL == true {
|
if *s3NoSSL == true {
|
||||||
|
@ -104,16 +118,16 @@ func InitConfig() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if *authExternal == true {
|
if *authExternal == true {
|
||||||
static["auth.external"] = "true"
|
static["auth.external.enabled"] = "true"
|
||||||
} else {
|
} else {
|
||||||
static["auth.external"] = "false"
|
static["auth.external.enabled"] = "false"
|
||||||
}
|
}
|
||||||
|
|
||||||
mappings := map[string]string{}
|
mappings := map[string]string{}
|
||||||
for name, value := range static {
|
for name := range static {
|
||||||
envName := strings.Replace(name, ".", "_")
|
envName := strings.ReplaceAll(name, ".", "_")
|
||||||
envName := strings.Replace(envName, "-", "_")
|
envName = strings.ReplaceAll(envName, "-", "_")
|
||||||
envName := strings.ToUpper(envName)
|
envName = strings.ToUpper(envName)
|
||||||
|
|
||||||
mappings[envName] = name
|
mappings[envName] = name
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,6 @@ type ConfigAuthenticationExternal struct {
|
||||||
|
|
||||||
type ConfigAuthentication struct {
|
type ConfigAuthentication struct {
|
||||||
External ConfigAuthenticationExternal `json:"external"`
|
External ConfigAuthenticationExternal `json:"external"`
|
||||||
LoginURL string `json:"login_url"`
|
|
||||||
LogoutURL string `json:"logout_url"`
|
LogoutURL string `json:"logout_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,8 +75,8 @@ func getConfig(c *gin.Context) {
|
||||||
resp.BasePath, _ = cfg.String("base.path")
|
resp.BasePath, _ = cfg.String("base.path")
|
||||||
resp.Authentication.LogoutURL, _ = cfg.String("auth.logout-url")
|
resp.Authentication.LogoutURL, _ = cfg.String("auth.logout-url")
|
||||||
resp.Authentication.External.Enabled, _ = cfg.Bool("auth.external")
|
resp.Authentication.External.Enabled, _ = cfg.Bool("auth.external")
|
||||||
resp.Authentication.External.AuthorizeURL, _ = cfg.String("auth.external-authorize-url")
|
resp.Authentication.External.AuthorizeURL, _ = cfg.String("auth.external.authorize-url")
|
||||||
resp.Authentication.External.ProviderName, _ = cfg.String("auth.external-provider-name")
|
resp.Authentication.External.ProviderName, _ = cfg.String("auth.external.provider-name")
|
||||||
resp.Title, _ = cfg.String("title")
|
resp.Title, _ = cfg.String("title")
|
||||||
resp.SubTitle, _ = cfg.String("sub-title")
|
resp.SubTitle, _ = cfg.String("sub-title")
|
||||||
resp.Contact.Name, _ = cfg.String("contact.name")
|
resp.Contact.Name, _ = cfg.String("contact.name")
|
||||||
|
|
|
@ -41,7 +41,7 @@ type tokenClaims struct {
|
||||||
|
|
||||||
func RegisterAuthenticate(r *gin.RouterGroup) {
|
func RegisterAuthenticate(r *gin.RouterGroup) {
|
||||||
r.GET("", authenticated)
|
r.GET("", authenticated)
|
||||||
r.POST("", authenticate)
|
r.POST("/:mechanism", authenticate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// authenticated godoc
|
// authenticated godoc
|
||||||
|
@ -105,48 +105,57 @@ func authenticated(c *gin.Context) {
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Tags authentication
|
// @Tags authentication
|
||||||
// @Param inputUser body user.loginRequest true "loginRequest of user"
|
// @Param inputUser body user.loginRequest true "loginRequest of user"
|
||||||
|
// @Param mechanism path string true "Login mechanism" Enums(internal, external)
|
||||||
// @Success 200 {object} api.ResponseAuthenticate "JSON web token, success status, message and authenticated user object"
|
// @Success 200 {object} api.ResponseAuthenticate "JSON web token, success status, message and authenticated user object"
|
||||||
// @Failure 401 {object} api.ResponseError "Unauthorized"
|
// @Failure 401 {object} api.ResponseError "Unauthorized"
|
||||||
// @Failure 500 {object} api.ResponseError "Internal server error."
|
// @Failure 500 {object} api.ResponseError "Internal server error."
|
||||||
// @Router /authenticate [post]
|
// @Router /authenticate{mechanism} [post]
|
||||||
func authenticate(c *gin.Context) {
|
func authenticate(c *gin.Context) {
|
||||||
var user *User
|
var user *User = nil
|
||||||
|
|
||||||
authExternal, err := configuration.GlobalConfig.Bool("auth.external")
|
switch c.Param("mechanism") {
|
||||||
if err != nil {
|
case "internal":
|
||||||
helper.UnauthorizedError(c, "Backend configuration error")
|
user = authenticateInternal(c)
|
||||||
return
|
case "external":
|
||||||
}
|
authExternal, err := configuration.GlobalConfig.Bool("auth.external")
|
||||||
|
if err == nil && authExternal {
|
||||||
if err != nil || !authExternal {
|
user = authenticateExternal(c)
|
||||||
user = authenticateStandard(c)
|
} else {
|
||||||
} else {
|
helper.BadRequestError(c, "External authentication is not activated")
|
||||||
user = authenticateExternal(c)
|
}
|
||||||
|
default:
|
||||||
|
helper.BadRequestError(c, "Invalid authentication mechanism")
|
||||||
}
|
}
|
||||||
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is an active user
|
||||||
|
if !user.Active {
|
||||||
|
helper.UnauthorizedError(c, "User is not active")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
expiresStr, err := configuration.GlobalConfig.String("jwt.expires-after")
|
expiresStr, err := configuration.GlobalConfig.String("jwt.expires-after")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.UnauthorizedError(c, "Backend configuration error")
|
helper.InternalServerError(c, "Invalid backend configuration: jwt.expires-after")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expiresDuration, err := time.ParseDuration(expiresStr)
|
expiresDuration, err := time.ParseDuration(expiresStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.UnauthorizedError(c, "Backend configuration error")
|
helper.InternalServerError(c, "Invalid backend configuration: jwt.expires-after")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
secret, err := configuration.GlobalConfig.String("jwt.secret")
|
secret, err := configuration.GlobalConfig.String("jwt.secret")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.UnauthorizedError(c, "Backend configuration error")
|
helper.InternalServerError(c, "Invalid backend configuration: jwt.secret")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// create authentication token
|
// Create authentication token
|
||||||
claims := tokenClaims{
|
claims := tokenClaims{
|
||||||
user.ID,
|
user.ID,
|
||||||
user.Role,
|
user.Role,
|
||||||
|
@ -161,7 +170,7 @@ func authenticate(c *gin.Context) {
|
||||||
|
|
||||||
tokenString, err := token.SignedString([]byte(secret))
|
tokenString, err := token.SignedString([]byte(secret))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServerError(c, err.Error())
|
helper.InternalServerError(c, "Invalid backend configuration: jwt.secret")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +182,7 @@ func authenticate(c *gin.Context) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func authenticateStandard(c *gin.Context) *User {
|
func authenticateInternal(c *gin.Context) *User {
|
||||||
// Bind the response (context) with the loginRequest struct
|
// Bind the response (context) with the loginRequest struct
|
||||||
var credentials loginRequest
|
var credentials loginRequest
|
||||||
if err := c.ShouldBindJSON(&credentials); err != nil {
|
if err := c.ShouldBindJSON(&credentials); err != nil {
|
||||||
|
@ -195,12 +204,6 @@ func authenticateStandard(c *gin.Context) *User {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is an active user
|
|
||||||
if !user.Active {
|
|
||||||
helper.UnauthorizedError(c, "User is not active")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the password
|
// Validate the password
|
||||||
err = user.validatePassword(credentials.Password)
|
err = user.validatePassword(credentials.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Add table
Reference in a new issue