13 Commits
7 changed files with 141 additions and 29 deletions

No files matched your search

+34
View File
@@ -8,3 +8,37 @@ OIDC_CLIENT_SECRET=<client secret>
OIDC_ISSUE=<issuer url> OIDC_ISSUE=<issuer url>
``` ```
If these env vars are not present, then depending on configuration, stubs will be used for the endpoints or the program will crash If these env vars are not present, then depending on configuration, stubs will be used for the endpoints or the program will crash
## Usage
```go
package main
import (
"os"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/memstore"
"github.com/gin-gonic/gin"
"github.com/janishutz/oidclogin"
)
func main() {
r := gin.Default()
// TODO: Better secret
store := memstore.NewStore([]byte("secret"))
r.Use(sessions.Sessions("jhid", store))
oidclogin.Configure(r, os.Getenv("APP_BASE_URL"), "/account", false)
r.LoadHTMLGlob("public/*")
r.GET("/account", oidclogin.EnsureLogin(false), func(ctx *gin.Context) {
ctx.HTML(200, "main.tmpl", gin.H{})
})
r.Run()
}
```
This example further needs the environment variable `APP_BASE_URL` set to something like `https://app.example.org`.
Further, you should create a template file called `main.tmpl` and also copy over the template files in the `public` directory here and edit them.
+22
View File
@@ -102,6 +102,8 @@ func callbackHandler(c *gin.Context) {
if userFunc != nil { if userFunc != nil {
userFunc(claims.Uid, claims.Name, claims.Email) userFunc(claims.Uid, claims.Name, claims.Email)
} else {
log.Println("[JHID] WARNING: No user function defined")
} }
// Clear session data of oauth related state // Clear session data of oauth related state
@@ -112,6 +114,7 @@ func callbackHandler(c *gin.Context) {
session.Delete("redirect") session.Delete("redirect")
session.Set("jhid_auth", true) session.Set("jhid_auth", true)
session.Set("jhid_uid", claims.Uid)
session.Save() session.Save()
if redir != nil { if redir != nil {
@@ -121,10 +124,29 @@ func callbackHandler(c *gin.Context) {
} }
} }
func logoutHandler(c *gin.Context) {
session := sessions.Default(c)
session.Clear()
session.Save()
redir := ""
if c.Query("returnTo") != "" {
redir = c.Query("returnTo")
}
if redir != "" {
c.Redirect(307, redir)
} else {
c.Redirect(307, defaultRedirect)
}
}
// Ensure that a user is currently logged in.
// If redirectFail is set, then if not, the user is redirected.
func EnsureLogin(redirectFail bool) func(c *gin.Context) { func EnsureLogin(redirectFail bool) func(c *gin.Context) {
return (func(c *gin.Context) { return (func(c *gin.Context) {
session := sessions.Default(c) session := sessions.Default(c)
if session.Get("jhid_auth") == true { if session.Get("jhid_auth") == true {
// Set the UID on the context
c.Set("uid", session.Get("jhid_uid").(string))
c.Next() c.Next()
} else { } else {
if redirectFail { if redirectFail {
+22 -4
View File
@@ -20,18 +20,30 @@ func createRandomString(n int) (string, error) {
return base64.URLEncoding.EncodeToString(s), nil return base64.URLEncoding.EncodeToString(s), nil
} }
type UserFunc func(userid string, name string, email string)
var ( var (
config oauth2.Config config oauth2.Config
userFunc func(userid string, name string, email string) userFunc UserFunc
verifier oidc.IDTokenVerifier verifier oidc.IDTokenVerifier
defaultRedirect string defaultRedirect string
) )
// Configure and set up the login SDK. // Wraps the normal configure function, but also gives you access to change the User Function, which is called upon login.
func Configure(r *gin.Engine, app_url string, default_redirect string, stubs_on_unconfigured bool) { // It is used to create or update a user.
// The check middleware may be nil, in which case a default is used. Otherwise should be a valid gin middleware, calling c.Next() if okay to proceed.
func ConfigureFull(r *gin.Engine, app_url string, default_redirect string, user_function UserFunc, stubs_on_unconfigured bool, check_middleware *func(c *gin.Context)) {
userFunc = user_function
Configure(r, app_url, default_redirect, stubs_on_unconfigured, check_middleware)
}
// Configure and set up the login SDK
// The check middleware may be nil, in which case a default is used. Otherwise should be a valid gin middleware, calling c.Next() if okay to proceed.
func Configure(r *gin.Engine, app_url string, default_redirect string, stubs_on_unconfigured bool, check_middleware *func(c *gin.Context)) {
issuer := os.Getenv("OIDC_ISSUER") issuer := os.Getenv("OIDC_ISSUER")
clientID := os.Getenv("OIDC_CLIENT_ID") clientID := os.Getenv("OIDC_CLIENT_ID")
clientSecret := os.Getenv("OIDC_CLIENT_SECRET") clientSecret := os.Getenv("OIDC_CLIENT_SECRET")
defaultRedirect = default_redirect
if issuer == "" || clientID == "" || clientSecret == "" { if issuer == "" || clientID == "" || clientSecret == "" {
if stubs_on_unconfigured { if stubs_on_unconfigured {
@@ -45,7 +57,6 @@ func Configure(r *gin.Engine, app_url string, default_redirect string, stubs_on_
provider, err := oidc.NewProvider(context.Background(), issuer) provider, err := oidc.NewProvider(context.Background(), issuer)
verifier = *provider.Verifier(&oidc.Config{ClientID: clientID}) verifier = *provider.Verifier(&oidc.Config{ClientID: clientID})
defaultRedirect = default_redirect
if err != nil { if err != nil {
log.Fatal("[JHID] Provider resolution failed with error", err) log.Fatal("[JHID] Provider resolution failed with error", err)
@@ -61,6 +72,13 @@ func Configure(r *gin.Engine, app_url string, default_redirect string, stubs_on_
r.GET("/auth/v2/login", loginHandler) r.GET("/auth/v2/login", loginHandler)
r.GET("/auth/v2/verify", callbackHandler) r.GET("/auth/v2/verify", callbackHandler)
check_finalizer := func(ctx *gin.Context) { ctx.JSON(200, gin.H{"success": "true"}) }
if check_middleware == nil {
r.GET("/auth/v2/check", EnsureLogin(false), check_finalizer)
} else {
r.GET("/auth/v2/check", EnsureLogin(false), *check_middleware, check_finalizer)
}
r.GET("/auth/v2/logout", logoutHandler)
log.Println("[JHID] Configured successfully") log.Println("[JHID] Configured successfully")
} }
+6 -2
View File
@@ -1,10 +1,14 @@
<!DOCTYPE html>
<html> <html>
<head> <head>
<!-- NOTE: You should definitely host this yourself! (and you can adjust the style that way, too!) --> <!-- NOTE: You should definitely host this yourself! (and you can adjust the style that way, too!) -->
<link rel="stylesheet" href="https://cdn.jsdelir.net/gh/janishutz/oidclogin/public/style.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/janishutz/oidclogin/public/style.css">
</head> </head>
<body> <body>
<h1>401</h1> <div class="message-wrapper">
<h1>401</h1>
<p>Unauthorized</p>
</div>
<p>You are not authorized to view this page. Please log in</p> <p>You are not authorized to view this page. Please log in</p>
<a href="/">Back Home</a> <a href="/">Back Home</a>
</body> </body>
+7 -3
View File
@@ -1,11 +1,15 @@
<!DOCTYPE html>
<html> <html>
<head> <head>
<!-- NOTE: You should definitely host this yourself! (and you can adjust the style that way, too!) --> <!-- NOTE: You should definitely host this yourself! (and you can adjust the style that way, too!) -->
<link rel="stylesheet" href="https://cdn.jsdelir.net/gh/janishutz/oidclogin/public/style.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/janishutz/oidclogin/public/style.css">
</head> </head>
<body> <body>
<h1>500</h1> <div class="message-wrapper">
<p>Login Failed!</p> <h1>500</h1>
<p>Internal Server Error</p>
</div>
<p>There was an error logging you in. Please try again. We are sorry for the inconvenience</p>
<a href="/">Back Home</a> <a href="/">Back Home</a>
<p class="error-msg">{{ .error }}</p> <p class="error-msg">{{ .error }}</p>
</body> </body>
+20 -3
View File
@@ -1,4 +1,5 @@
html, body { html,
body {
background-color: #050505; background-color: #050505;
color: #FFFFFF; color: #FFFFFF;
width: 100vw; width: 100vw;
@@ -12,16 +13,32 @@ body {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-direction: column;
} }
h1 { .message-wrapper {
display: flex;
justify-content: center;
align-items: center;
}
.message-wrapper h1 {
font-size: 10rem; font-size: 10rem;
} }
p { .message-wrapper p {
font-size: 1.5rem; font-size: 1.5rem;
} }
a {
color: white;
background-color: #101066;
padding: 10px;
border-radius: 10px;
cursor: pointer;
text-decoration: none;
}
.error-msg { .error-msg {
font-size: 0.8rem; font-size: 0.8rem;
position: fixed; position: fixed;
+30 -17
View File
@@ -1,22 +1,35 @@
package oidclogin package oidclogin
import "github.com/gin-gonic/gin" import (
"log"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
func startStubs(r *gin.Engine) { func startStubs(r *gin.Engine) {
r.GET("/auth/v2/login", func(ctx *gin.Context) { r.GET("/auth/v2/login", stubsHandler)
redir := ctx.Query("returnTo") r.GET("/auth/v2/verify", stubsHandler)
if redir != "" { r.GET("/auth/v2/check", EnsureLogin(false), func(ctx *gin.Context) { ctx.JSON(200, gin.H{"success": "true"}) })
ctx.Redirect(307, redir) r.GET("/auth/v2/logout", logoutHandler)
} else { }
ctx.Redirect(307, defaultRedirect)
} func stubsHandler(c *gin.Context) {
}) redir := c.Query("returnTo")
r.GET("/auth/v2/verify", func(ctx *gin.Context) { session := sessions.Default(c)
redir := ctx.Query("returnTo") if userFunc != nil {
if redir != "" { userFunc("stubs", "Stubs User", "example@example.com")
ctx.Redirect(307, redir) } else {
} else { log.Println("[JHID] WARNING: No user function defined")
ctx.Redirect(307, defaultRedirect) }
} session.Set("jhid_auth", true)
}) session.Set("jhid_uid", "stubs")
session.Save()
if redir != "" {
log.Println("[JHID] Redirecting to ", redir)
c.Redirect(307, redir)
} else {
log.Println("[JHID] Redirecting to ", defaultRedirect, " (default redirect)")
c.Redirect(307, defaultRedirect)
}
} }