Skip to content

Commit

Permalink
Merge pull request #5 from echo-webkom/sample_auth
Browse files Browse the repository at this point in the history
Sample auth
  • Loading branch information
jesperkha authored Apr 22, 2024
2 parents b48625b + 3454429 commit c532362
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 22 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/echo-webkom/goat

go 1.21.1
go 1.22.1

require github.com/markbates/goth v1.79.0

Expand Down
125 changes: 125 additions & 0 deletions internal/auth/sample/sample.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package sample

import (
"context"
"encoding/json"
"io"
"log"
"net/http"

"golang.org/x/oauth2"
)

func resJson(w http.ResponseWriter, j any) {
b, err := json.Marshal(j)
if err != nil {
log.Println("JSON marshal error: " + err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}

w.Header().Add("Content-Type", "application/json")
w.Write(b)
}

// Mount endpoints handled by provider for testing
func mountExampleHandlers(s *http.ServeMux) {
// Example login page, will be replaced with provider URL
s.HandleFunc("GET /sample/auth", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "internal/auth/sample/sample_auth.html")
})

// Used for token exchange
s.HandleFunc("POST /sample/tokenUrl", func(w http.ResponseWriter, r *http.Request) {
resJson(w, map[string]any{
"access_token": "abcdef",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "ghijklmno",
"scope": "",
})
})

// Used to fetch user data with generated token
s.HandleFunc("GET /sample/tokenUrl", func(w http.ResponseWriter, r *http.Request) {
resJson(w, map[string]any{
"username": "bob",
"access_token": r.URL.Query().Get("access_token"),
})
})
}

// Todo: create generic newProvider function

func New(s *http.ServeMux) {
mountExampleHandlers(s)

const (
// Load from .env
CLIENT_ID = "john"
CLIENT_SECRET = "1234"

AUTH_URL = "http://localhost:8080/sample/auth"
TOKEN_URL = "http://localhost:8080/sample/tokenUrl"
)

config := oauth2.Config{
RedirectURL: "http://localhost:8080/sample_callback",
ClientID: CLIENT_ID,
ClientSecret: CLIENT_SECRET,
Scopes: []string{},
Endpoint: oauth2.Endpoint{
AuthURL: AUTH_URL,
TokenURL: TOKEN_URL,
},
}

s.Handle("GET /sample_login", login(config))
s.Handle("POST /sample_callback", callback(config))
}

// Creates new login handler. Should redirect to providers auth URL with
// generated state. URL is given client id/secret, redirect uri, callback
// uri and state.
func login(config oauth2.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
url := config.AuthCodeURL("randomstate")
http.Redirect(w, r, url, http.StatusSeeOther)
}
}

// Creates a new callback handler for the auth provider. Verifies state
// and creates access token from code given by provider. This handler simply
// responds with the user data json.
func callback(config oauth2.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
state := r.FormValue("state")
if state != "randomstate" {
w.WriteHeader(http.StatusInternalServerError)
return
}

code := r.FormValue("code")

token, err := config.Exchange(context.Background(), code)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

token_url := config.Endpoint.TokenURL + "?access_token=" + token.AccessToken
resp, err := http.Get(token_url)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

userData, err := io.ReadAll(resp.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

w.Write(userData)
}
}
19 changes: 19 additions & 0 deletions internal/auth/sample/sample_auth.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p>hello auth</p>

<form action="/sample_callback" method="post">
<label for="code">Code:</label><br>
<input type="text" id="code" name="code" value="1234"><br>
<label for="state">State:</label><br>
<input type="text" id="state" name="state" value="randomstate"><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
17 changes: 11 additions & 6 deletions internal/server/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,29 @@ package server

import "net/http"

type HandlerWithCtx struct {
res http.ResponseWriter
req *http.Request
type Context struct {
res http.ResponseWriter
req *http.Request

name string
}

type HandlerFunc func(HandlerWithCtx)
type HandlerFunc func(Context)

func NewHandler(f HandlerFunc) HandlerFunc {
return f
}

func ToHttpHandler(f HandlerFunc) http.HandlerFunc {
func ToHttpHandlerFunc(f HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
f(HandlerWithCtx{w, r, ""}) // Create empty base context
f(Context{w, r, ""}) // Create empty base context
}
}

func ToHttpHandler(f HandlerFunc) http.Handler {
return ToHttpHandlerFunc(f)
}

type Middleware func(HandlerFunc) HandlerFunc

func NewMiddleware(m Middleware) Middleware {
Expand Down
33 changes: 18 additions & 15 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ package server
import (
"net/http"

"github.com/go-chi/chi/v5"
"github.com/echo-webkom/goat/internal/auth/sample"
)

type Config struct {
Addr string
type Server struct {
Router *http.ServeMux
Config Config
}

type Server struct {
Router *chi.Mux
Config *Config
type Config struct {
Addr string
}

func New() *Server {
r := chi.NewRouter()
r := http.NewServeMux()

cfg := &Config{
cfg := Config{
Addr: ":8080",
}

Expand All @@ -35,17 +35,20 @@ func (s *Server) Run(addr string) error {
func (s *Server) MountHandlers() {

// Create simple base handler using a context
handler := NewHandler(func(hwc HandlerWithCtx) {
hwc.res.Write([]byte("Hello " + hwc.name))
handler := NewHandler(func(ctx Context) {
ctx.res.Write([]byte("Hello " + ctx.name))
})

// Create middleware that writes to the context before calling the handler
// Create myMiddleware that writes to the context before calling the handler
middleware := NewMiddleware(func(hf HandlerFunc) HandlerFunc {
return func(hwc HandlerWithCtx) {
hwc.name = "John"
hf(hwc)
return func(ctx Context) {
ctx.name = "John"
hf(ctx)
}
})

s.Router.Get("/", ToHttpHandler(middleware(handler)))
s.Router.Handle("GET /test", ToHttpHandlerFunc(middleware(handler)))

// Sample oauth2 flow, go to /sample_login
sample.New(s.Router)
}

0 comments on commit c532362

Please sign in to comment.