Add SSO login support
- Add [sso] config section with redirect_uri - Create mcdsl/sso client when SSO is configured - Add /login (landing page), /sso/redirect, /sso/callback routes - Add /logout route - Update login template with SSO landing page variant - Bump mcdsl to v1.6.0 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
21
vendor/git.wntrmute.dev/kyle/goutils/lib/defs.go
vendored
Normal file
21
vendor/git.wntrmute.dev/kyle/goutils/lib/defs.go
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
package lib
|
||||
|
||||
// Various constants used throughout the tools.
|
||||
|
||||
const (
|
||||
// ExitSuccess is the successful exit status.
|
||||
//
|
||||
// It should be called on successful exit.
|
||||
ExitSuccess = 0
|
||||
|
||||
// ExitFailure is the failing exit status.
|
||||
ExitFailure = 1
|
||||
)
|
||||
|
||||
const (
|
||||
OneTrueDateFormat = "2006-01-02T15:04:05-0700"
|
||||
DateShortFormat = "2006-01-02"
|
||||
TimeShortFormat = "15:04:05"
|
||||
TimeShorterFormat = "15:04"
|
||||
TimeStandardDateTime = "2006-01-02 15:04"
|
||||
)
|
||||
37
vendor/git.wntrmute.dev/kyle/goutils/lib/ftime_bsd.go
vendored
Normal file
37
vendor/git.wntrmute.dev/kyle/goutils/lib/ftime_bsd.go
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
//go:build bsd
|
||||
|
||||
package lib
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// FileTime contains the changed, modified, and accessed timestamps
|
||||
// for a file.
|
||||
type FileTime struct {
|
||||
Changed time.Time
|
||||
Modified time.Time
|
||||
Accessed time.Time
|
||||
}
|
||||
|
||||
func timeSpecToTime(ts unix.Timespec) time.Time {
|
||||
return time.Unix(ts.Sec, ts.Nsec)
|
||||
}
|
||||
|
||||
// LoadFileTime returns a FileTime associated with the file.
|
||||
func LoadFileTime(path string) (FileTime, error) {
|
||||
var ft = FileTime{}
|
||||
var st = unix.Stat_t{}
|
||||
|
||||
err := unix.Stat(path, &st)
|
||||
if err != nil {
|
||||
return ft, err
|
||||
}
|
||||
|
||||
ft.Changed = timeSpecToTime(st.Ctimespec)
|
||||
ft.Modified = timeSpecToTime(st.Mtimespec)
|
||||
ft.Accessed = timeSpecToTime(st.Atimespec)
|
||||
return ft, nil
|
||||
}
|
||||
38
vendor/git.wntrmute.dev/kyle/goutils/lib/ftime_unix.go
vendored
Normal file
38
vendor/git.wntrmute.dev/kyle/goutils/lib/ftime_unix.go
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
//go:build unix || linux || openbsd || (darwin && amd64)
|
||||
|
||||
package lib
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// FileTime contains the changed, modified, and accessed timestamps
|
||||
// for a file.
|
||||
type FileTime struct {
|
||||
Changed time.Time
|
||||
Modified time.Time
|
||||
Accessed time.Time
|
||||
}
|
||||
|
||||
func timeSpecToTime(ts unix.Timespec) time.Time {
|
||||
// The casts to int64 are needed because on 386, these are int32s.
|
||||
return time.Unix(ts.Sec, ts.Nsec)
|
||||
}
|
||||
|
||||
// LoadFileTime returns a FileTime associated with the file.
|
||||
func LoadFileTime(path string) (FileTime, error) {
|
||||
var ft = FileTime{}
|
||||
var st = unix.Stat_t{}
|
||||
|
||||
err := unix.Stat(path, &st)
|
||||
if err != nil {
|
||||
return ft, err
|
||||
}
|
||||
|
||||
ft.Changed = timeSpecToTime(st.Ctim)
|
||||
ft.Modified = timeSpecToTime(st.Mtim)
|
||||
ft.Accessed = timeSpecToTime(st.Atim)
|
||||
return ft, nil
|
||||
}
|
||||
349
vendor/git.wntrmute.dev/kyle/goutils/lib/lib.go
vendored
Normal file
349
vendor/git.wntrmute.dev/kyle/goutils/lib/lib.go
vendored
Normal file
@@ -0,0 +1,349 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var progname = filepath.Base(os.Args[0])
|
||||
|
||||
const (
|
||||
daysInYear = 365
|
||||
digitWidth = 10
|
||||
hoursInQuarterDay = 6
|
||||
)
|
||||
|
||||
// ProgName returns what lib thinks the program name is, namely the
|
||||
// basename of argv0.
|
||||
//
|
||||
// It is similar to the Linux __progname function.
|
||||
func ProgName() string {
|
||||
return progname
|
||||
}
|
||||
|
||||
// Warnx displays a formatted error message to standard error, à la
|
||||
// warnx(3).
|
||||
func Warnx(format string, a ...any) (int, error) {
|
||||
format = fmt.Sprintf("[%s] %s", progname, format)
|
||||
format += "\n"
|
||||
return fmt.Fprintf(os.Stderr, format, a...)
|
||||
}
|
||||
|
||||
// Warn displays a formatted error message to standard output,
|
||||
// appending the error string, à la warn(3).
|
||||
func Warn(err error, format string, a ...any) (int, error) {
|
||||
format = fmt.Sprintf("[%s] %s", progname, format)
|
||||
format += ": %v\n"
|
||||
a = append(a, err)
|
||||
return fmt.Fprintf(os.Stderr, format, a...)
|
||||
}
|
||||
|
||||
// Errx displays a formatted error message to standard error and exits
|
||||
// with the status code from `exit`, à la errx(3).
|
||||
func Errx(exit int, format string, a ...any) {
|
||||
format = fmt.Sprintf("[%s] %s", progname, format)
|
||||
format += "\n"
|
||||
fmt.Fprintf(os.Stderr, format, a...)
|
||||
os.Exit(exit)
|
||||
}
|
||||
|
||||
// Err displays a formatting error message to standard error,
|
||||
// appending the error string, and exits with the status code from
|
||||
// `exit`, à la err(3).
|
||||
func Err(exit int, err error, format string, a ...any) {
|
||||
format = fmt.Sprintf("[%s] %s", progname, format)
|
||||
format += ": %v\n"
|
||||
a = append(a, err)
|
||||
fmt.Fprintf(os.Stderr, format, a...)
|
||||
os.Exit(exit)
|
||||
}
|
||||
|
||||
// Itoa provides cheap integer to fixed-width decimal ASCII. Give a
|
||||
// negative width to avoid zero-padding. Adapted from the 'itoa'
|
||||
// function in the log/log.go file in the standard library.
|
||||
func Itoa(i int, wid int) string {
|
||||
// Assemble decimal in reverse order.
|
||||
var b [20]byte
|
||||
bp := len(b) - 1
|
||||
for i >= digitWidth || wid > 1 {
|
||||
wid--
|
||||
q := i / digitWidth
|
||||
b[bp] = byte('0' + i - q*digitWidth)
|
||||
bp--
|
||||
i = q
|
||||
}
|
||||
|
||||
b[bp] = byte('0' + i)
|
||||
return string(b[bp:])
|
||||
}
|
||||
|
||||
var (
|
||||
dayDuration = 24 * time.Hour
|
||||
yearDuration = (daysInYear * dayDuration) + (hoursInQuarterDay * time.Hour)
|
||||
)
|
||||
|
||||
// Duration returns a prettier string for time.Durations.
|
||||
func Duration(d time.Duration) string {
|
||||
var s string
|
||||
if d >= yearDuration {
|
||||
years := int64(d / yearDuration)
|
||||
s += fmt.Sprintf("%dy", years)
|
||||
d -= time.Duration(years) * yearDuration
|
||||
}
|
||||
|
||||
if d >= dayDuration {
|
||||
days := d / dayDuration
|
||||
s += fmt.Sprintf("%dd", days)
|
||||
}
|
||||
|
||||
if s != "" {
|
||||
return s
|
||||
}
|
||||
|
||||
d %= 1 * time.Second
|
||||
hours := int64(d / time.Hour)
|
||||
d -= time.Duration(hours) * time.Hour
|
||||
s += fmt.Sprintf("%dh%s", hours, d)
|
||||
return s
|
||||
}
|
||||
|
||||
// IsDigit checks if a byte is a decimal digit.
|
||||
func IsDigit(b byte) bool {
|
||||
return b >= '0' && b <= '9'
|
||||
}
|
||||
|
||||
const signedaMask64 = 1<<63 - 1
|
||||
|
||||
// ParseDuration parses a duration string into a time.Duration.
|
||||
// It supports standard units (ns, us/µs, ms, s, m, h) plus extended units:
|
||||
// d (days, 24h), w (weeks, 7d), y (years, 365d).
|
||||
// Units can be combined without spaces, e.g., "1y2w3d4h5m6s".
|
||||
// Case-insensitive. Years and days are approximations (no leap seconds/months).
|
||||
// Returns an error for invalid input.
|
||||
func ParseDuration(s string) (time.Duration, error) {
|
||||
s = strings.ToLower(s) // Normalize to lowercase for case-insensitivity.
|
||||
if s == "" {
|
||||
return 0, errors.New("empty duration string")
|
||||
}
|
||||
|
||||
var total time.Duration
|
||||
i := 0
|
||||
for i < len(s) {
|
||||
// Parse the number part.
|
||||
start := i
|
||||
for i < len(s) && IsDigit(s[i]) {
|
||||
i++
|
||||
}
|
||||
if start == i {
|
||||
return 0, fmt.Errorf("expected number at position %d", start)
|
||||
}
|
||||
numStr := s[start:i]
|
||||
num, err := strconv.ParseUint(numStr, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid number %q: %w", numStr, err)
|
||||
}
|
||||
|
||||
// Parse the unit part.
|
||||
if i >= len(s) {
|
||||
return 0, fmt.Errorf("expected unit after number %q", numStr)
|
||||
}
|
||||
unitStart := i
|
||||
i++ // Consume the first char of the unit.
|
||||
unit := s[unitStart:i]
|
||||
|
||||
// Handle potential two-char units like "ms".
|
||||
if unit == "m" && i < len(s) && s[i] == 's' {
|
||||
i++ // Consume the 's'.
|
||||
unit = "ms"
|
||||
}
|
||||
|
||||
// Convert to duration based on unit.
|
||||
var d time.Duration
|
||||
switch unit {
|
||||
case "ns":
|
||||
d = time.Nanosecond * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "us", "µs":
|
||||
d = time.Microsecond * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "ms":
|
||||
d = time.Millisecond * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "s":
|
||||
d = time.Second * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "m":
|
||||
d = time.Minute * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "h":
|
||||
d = time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "d":
|
||||
d = 24 * time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "w":
|
||||
d = 7 * 24 * time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off
|
||||
case "y":
|
||||
// Approximate, non-leap year.
|
||||
d = 365 * 24 * time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off;
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown unit %q at position %d", s[unitStart:i], unitStart)
|
||||
}
|
||||
|
||||
total += d
|
||||
}
|
||||
|
||||
return total, nil
|
||||
}
|
||||
|
||||
type HexEncodeMode uint8
|
||||
|
||||
const (
|
||||
// HexEncodeLower prints the bytes as lowercase hexadecimal.
|
||||
HexEncodeLower HexEncodeMode = iota + 1
|
||||
// HexEncodeUpper prints the bytes as uppercase hexadecimal.
|
||||
HexEncodeUpper
|
||||
// HexEncodeLowerColon prints the bytes as lowercase hexadecimal
|
||||
// with colons between each pair of bytes.
|
||||
HexEncodeLowerColon
|
||||
// HexEncodeUpperColon prints the bytes as uppercase hexadecimal
|
||||
// with colons between each pair of bytes.
|
||||
HexEncodeUpperColon
|
||||
// HexEncodeBytes prints the string as a sequence of []byte.
|
||||
HexEncodeBytes
|
||||
// HexEncodeBase64 prints the string as a base64-encoded string.
|
||||
HexEncodeBase64
|
||||
)
|
||||
|
||||
func (m HexEncodeMode) String() string {
|
||||
switch m {
|
||||
case HexEncodeLower:
|
||||
return "lower"
|
||||
case HexEncodeUpper:
|
||||
return "upper"
|
||||
case HexEncodeLowerColon:
|
||||
return "lcolon"
|
||||
case HexEncodeUpperColon:
|
||||
return "ucolon"
|
||||
case HexEncodeBytes:
|
||||
return "bytes"
|
||||
case HexEncodeBase64:
|
||||
return "base64"
|
||||
default:
|
||||
panic("invalid hex encode mode")
|
||||
}
|
||||
}
|
||||
|
||||
func ParseHexEncodeMode(s string) HexEncodeMode {
|
||||
switch strings.ToLower(s) {
|
||||
case "lower":
|
||||
return HexEncodeLower
|
||||
case "upper":
|
||||
return HexEncodeUpper
|
||||
case "lcolon":
|
||||
return HexEncodeLowerColon
|
||||
case "ucolon":
|
||||
return HexEncodeUpperColon
|
||||
case "bytes":
|
||||
return HexEncodeBytes
|
||||
case "base64":
|
||||
return HexEncodeBase64
|
||||
}
|
||||
|
||||
panic("invalid hex encode mode")
|
||||
}
|
||||
|
||||
func hexColons(s string) string {
|
||||
if len(s)%2 != 0 {
|
||||
fmt.Fprintf(os.Stderr, "hex string: %s\n", s)
|
||||
fmt.Fprintf(os.Stderr, "hex length: %d\n", len(s))
|
||||
panic("invalid hex string length")
|
||||
}
|
||||
|
||||
n := len(s)
|
||||
if n <= 2 {
|
||||
return s
|
||||
}
|
||||
|
||||
pairCount := n / 2
|
||||
if n%2 != 0 {
|
||||
pairCount++
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.Grow(n + pairCount - 1)
|
||||
|
||||
for i := 0; i < n; i += 2 {
|
||||
b.WriteByte(s[i])
|
||||
|
||||
if i+1 < n {
|
||||
b.WriteByte(s[i+1])
|
||||
}
|
||||
|
||||
if i+2 < n {
|
||||
b.WriteByte(':')
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func hexEncode(b []byte) string {
|
||||
s := hex.EncodeToString(b)
|
||||
|
||||
if len(s)%2 != 0 {
|
||||
s = "0" + s
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func bytesAsByteSliceString(buf []byte) string {
|
||||
sb := &strings.Builder{}
|
||||
sb.WriteString("[]byte{")
|
||||
for i := range buf {
|
||||
fmt.Fprintf(sb, "0x%02x, ", buf[i])
|
||||
}
|
||||
sb.WriteString("}")
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// HexEncode encodes the given bytes as a hexadecimal string. It
|
||||
// also supports a few other binary-encoding formats as well.
|
||||
func HexEncode(b []byte, mode HexEncodeMode) string {
|
||||
switch mode {
|
||||
case HexEncodeLower:
|
||||
return hexEncode(b)
|
||||
case HexEncodeUpper:
|
||||
return strings.ToUpper(hexEncode(b))
|
||||
case HexEncodeLowerColon:
|
||||
return hexColons(hexEncode(b))
|
||||
case HexEncodeUpperColon:
|
||||
return strings.ToUpper(hexColons(hexEncode(b)))
|
||||
case HexEncodeBytes:
|
||||
return bytesAsByteSliceString(b)
|
||||
case HexEncodeBase64:
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
default:
|
||||
panic("invalid hex encode mode")
|
||||
}
|
||||
}
|
||||
|
||||
// DummyWriteCloser wraps an io.Writer in a struct with a no-op Close.
|
||||
type DummyWriteCloser struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func WithCloser(w io.Writer) io.WriteCloser {
|
||||
return &DummyWriteCloser{w: w}
|
||||
}
|
||||
|
||||
func (dwc *DummyWriteCloser) Write(p []byte) (int, error) {
|
||||
return dwc.w.Write(p)
|
||||
}
|
||||
|
||||
func (dwc *DummyWriteCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user