Lindenii Project Forge
Login

server

Vireo IdP server

Hi… I am well aware that this diff view is very suboptimal. It will be fixed when the refactored server comes along!

Commit info
ID
6d42dd89a647425acf401c8ecaccc7a6ed27186f
Author
Author date
Mon, 19 Feb 2024 01:11:41 +0100
Committer
Committer date
Mon, 19 Feb 2024 01:11:41 +0100
Actions
fixup! Add template to context
package main

import (
	"context"
	"fmt"
	"html/template"
	"net/http"
)

const internalTokenScope = "_sinwon"

type contextKey string

const (
	contextKeyDB         = "db"
	contextKeyTemplate   = "template"
	contextKeyLoginToken = "login-token"
)

func dbFromContext(ctx context.Context) *DB {
	return ctx.Value(contextKeyDB).(*DB)
}

func templateFromContext(ctx context.Context) *template.Template {
	return ctx.Value(contextKeyTemplate).(*template.Template)
}

func loginTokenFromContext(ctx context.Context) *AccessToken {
	v := ctx.Value(contextKeyLoginToken)
	if v == nil {
		return nil
	}
	return v.(*AccessToken)
}

func newBaseContext(db *DB, tpl *template.Template) context.Context {
	ctx := context.Background()
	ctx = context.WithValue(ctx, contextKeyDB, db)
	ctx = context.WithValue(ctx, contextKeyLoginToken, tpl)
	ctx = context.WithValue(ctx, contextKeyTemplate, tpl)
	return ctx
}

func setLoginTokenCookie(w http.ResponseWriter, token *AccessToken, secret string) {
	http.SetCookie(w, &http.Cookie{
		Name:     "sinwon-token",
		Value:    MarshalSecret(token.ID, secret),
		HttpOnly: true,
		SameSite: http.SameSiteStrictMode,
		// TODO: Secure
	})
}

func loginTokenMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		cookie, _ := req.Cookie("sinwon-token")
		if cookie == nil {
			next.ServeHTTP(w, req)
			return
		}

		ctx := req.Context()
		db := dbFromContext(ctx)
		tokenID, tokenSecret, _ := UnmarshalSecret[*AccessToken](cookie.Value)
		token, err := db.FetchAccessToken(ctx, tokenID)
		if err == errNoDBRows || (err == nil && !token.VerifySecret(tokenSecret)) {
			http.SetCookie(w, &http.Cookie{
				Name:     "sinwon-token",
				HttpOnly: true,
				SameSite: http.SameSiteStrictMode,
				MaxAge:   -1,
			})
			next.ServeHTTP(w, req)
			return
		} else if err != nil {
			httpError(w, fmt.Errorf("failed to fetch access token: %v", err))
			return
		}

		if token.Scope != internalTokenScope {
			http.Error(w, "Invalid login token scope", http.StatusForbidden)
			return
		}
		if token.User == 0 {
			panic("login token with zero user ID")
		}

		ctx = context.WithValue(ctx, contextKeyLoginToken, token)
		req = req.WithContext(ctx)
		next.ServeHTTP(w, req)
	})
}