This commit is contained in:
parent
25a37ba90d
commit
4fef573447
|
@ -33,7 +33,7 @@ COPY Makefile ./
|
|||
|
||||
# Build the frontend
|
||||
COPY frontend frontend
|
||||
RUN VITE_API_URL=/api npm run --prefix frontend build
|
||||
RUN VITE_API_URL=/api VITE_OIDC_URL=/oidc npm run --prefix frontend build
|
||||
|
||||
# Build backend
|
||||
COPY goshort.go ./
|
||||
|
|
6
Makefile
6
Makefile
|
@ -9,7 +9,7 @@ install-backend:
|
|||
install: install-frontend install-backend
|
||||
|
||||
frontend:
|
||||
VITE_API_URL=/api npm run --prefix frontend build
|
||||
VITE_API_URL=/api VITE_OIDC_URL=/oidc npm run --prefix frontend build
|
||||
|
||||
VERSION=$(shell git describe --tags --abbrev=0)
|
||||
|
||||
|
@ -25,10 +25,10 @@ dev:
|
|||
go run goshort.go dev
|
||||
|
||||
lint-frontend:
|
||||
VITE_API_URL=/api npm run --prefix frontend lint
|
||||
VITE_API_URL=/api VITE_OIDC_URL=/oidc npm run --prefix frontend lint
|
||||
|
||||
lint-frontend-fix:
|
||||
VITE_API_URL=/api npm run --prefix frontend lint:fix
|
||||
VITE_API_URL=/api VITE_OIDC_URL=/oidc npm run --prefix frontend lint:fix
|
||||
|
||||
lint-backend:
|
||||
golangci-lint run
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
oidcserver "git.maronato.dev/maronato/goshort/internal/server/oidc"
|
||||
shortserver "git.maronato.dev/maronato/goshort/internal/server/short"
|
||||
staticssterver "git.maronato.dev/maronato/goshort/internal/server/static"
|
||||
configservice "git.maronato.dev/maronato/goshort/internal/service/config"
|
||||
oidcservice "git.maronato.dev/maronato/goshort/internal/service/oidc"
|
||||
shortservice "git.maronato.dev/maronato/goshort/internal/service/short"
|
||||
shortlogservice "git.maronato.dev/maronato/goshort/internal/service/shortlog"
|
||||
|
@ -105,17 +106,18 @@ func serveAPI(ctx context.Context, cfg *config.Config) error {
|
|||
tokenService := tokenservice.NewTokenService(storage)
|
||||
shortLogService := shortlogservice.NewShortLogService(storage)
|
||||
oidcService := oidcservice.NewOIDCService(ctx, cfg)
|
||||
configService := configservice.NewConfigService(cfg)
|
||||
|
||||
// Start short log worker
|
||||
stopWorker, _ := shortLogService.StartWorker(ctx)
|
||||
defer stopWorker()
|
||||
|
||||
// Create handlers
|
||||
apiHandler := apiserver.NewAPIHandler(shortService, userService, tokenService, shortLogService)
|
||||
apiHandler := apiserver.NewAPIHandler(shortService, userService, tokenService, shortLogService, configService)
|
||||
shortHandler := shortserver.NewShortHandler(shortService, shortLogService)
|
||||
healthcheckHandler := healthcheckserver.NewHealthcheckHandler(storage)
|
||||
docsHandler := staticssterver.NewStaticHandler(cfg, "/api/docs", docs.Assets())
|
||||
oidcHandler := oidcserver.NewOIDCHandler(oidcService, userService)
|
||||
oidcHandler := oidcserver.NewOIDCHandler(cfg, oidcService, userService)
|
||||
|
||||
// Create routers
|
||||
apiRouter := apiserver.NewAPIRouter(apiHandler)
|
||||
|
@ -146,6 +148,7 @@ func serveAPI(ctx context.Context, cfg *config.Config) error {
|
|||
if oidcService != nil {
|
||||
srv.Mux.Mount("/oidc", oidcRouter)
|
||||
}
|
||||
|
||||
srv.Mux.Mount("/healthz", healthcheckRouter)
|
||||
srv.Mux.Mount("/", chainedRouter)
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
oidcserver "git.maronato.dev/maronato/goshort/internal/server/oidc"
|
||||
shortserver "git.maronato.dev/maronato/goshort/internal/server/short"
|
||||
staticssterver "git.maronato.dev/maronato/goshort/internal/server/static"
|
||||
configservice "git.maronato.dev/maronato/goshort/internal/service/config"
|
||||
oidcservice "git.maronato.dev/maronato/goshort/internal/service/oidc"
|
||||
shortservice "git.maronato.dev/maronato/goshort/internal/service/short"
|
||||
shortlogservice "git.maronato.dev/maronato/goshort/internal/service/shortlog"
|
||||
|
@ -78,18 +79,19 @@ func exec(ctx context.Context, cfg *config.Config) error {
|
|||
tokenService := tokenservice.NewTokenService(storage)
|
||||
shortLogService := shortlogservice.NewShortLogService(storage)
|
||||
oidcService := oidcservice.NewOIDCService(ctx, cfg)
|
||||
configService := configservice.NewConfigService(cfg)
|
||||
|
||||
// Start short log worker
|
||||
stopWorker, _ := shortLogService.StartWorker(ctx)
|
||||
defer stopWorker()
|
||||
|
||||
// Create handlers
|
||||
apiHandler := apiserver.NewAPIHandler(shortService, userService, tokenService, shortLogService)
|
||||
apiHandler := apiserver.NewAPIHandler(shortService, userService, tokenService, shortLogService, configService)
|
||||
shortHandler := shortserver.NewShortHandler(shortService, shortLogService)
|
||||
staticHandler := staticssterver.NewStaticHandler(cfg, "/", frontend.Assets())
|
||||
healthcheckHandler := healthcheckserver.NewHealthcheckHandler(storage)
|
||||
docsHandler := staticssterver.NewStaticHandler(cfg, "/api/docs", docs.Assets())
|
||||
oidcHandler := oidcserver.NewOIDCHandler(oidcService, userService)
|
||||
oidcHandler := oidcserver.NewOIDCHandler(cfg, oidcService, userService)
|
||||
|
||||
// Create routers
|
||||
apiRouter := apiserver.NewAPIRouter(apiHandler)
|
||||
|
|
|
@ -71,6 +71,7 @@ func RegisterServerFlags(fs *flag.FlagSet, cfg *config.Config) {
|
|||
fs.StringVar(&cfg.OIDCClientID, "oidc-client-id", "", "OIDC client ID")
|
||||
fs.StringVar(&cfg.OIDCClientSecret, "oidc-client-secret", "", "OIDC client secret")
|
||||
fs.StringVar(&cfg.OIDCRedirectURL, "oidc-redirect-url", "", "OIDC redirect URL")
|
||||
fs.StringVar(&cfg.OIDCIssuerName, "oidc-issuer-name", config.DefaultOIDCIssuerName, "OIDC issuer name")
|
||||
}
|
||||
|
||||
// InitStorage initializes the storage depending on the config.
|
||||
|
|
|
@ -8,6 +8,8 @@ import {
|
|||
useSearchParams,
|
||||
} from "react-router-dom"
|
||||
|
||||
import { useServerConfig } from "../hooks/useServerConfig"
|
||||
|
||||
import Button from "./Button"
|
||||
|
||||
const actionOptions = {
|
||||
|
@ -102,6 +104,7 @@ const UserForm: FunctionComponent<{
|
|||
}> = ({ action }) => {
|
||||
const [params] = useSearchParams()
|
||||
const from = params.get("from") || "/"
|
||||
const serverConfig = useServerConfig()
|
||||
|
||||
const opts = actionOptions[action]
|
||||
|
||||
|
@ -128,89 +131,99 @@ const UserForm: FunctionComponent<{
|
|||
const passwordScore = useMemo(() => passwordOMeter(password), [password])
|
||||
|
||||
return (
|
||||
<Form
|
||||
method="post"
|
||||
replace
|
||||
className="flex flex-col mx-auto gap-y-4 rounded-lg shadow-md p-8 max-w-min">
|
||||
<div className="flex flex-col mx-auto rounded-lg shadow-md p-8 max-w-md gap-y-6">
|
||||
<span className="text-3xl font-bold text-center mb-4">{opts.title}</span>
|
||||
{actionData && actionData.error ? (
|
||||
<p className="text-red-500 text-center font-medium">
|
||||
{actionData.error}
|
||||
</p>
|
||||
) : null}
|
||||
<input type="hidden" name="redirectTo" value={from} />
|
||||
<section className="flex flex-col">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="font-medium text-slate-600 -mb-2 z-10 text-lg">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
autoFocus
|
||||
id="email"
|
||||
name="username"
|
||||
autoComplete="username"
|
||||
type="email"
|
||||
required
|
||||
placeholder=" "
|
||||
minLength={4}
|
||||
maxLength={128}
|
||||
className="p-1 font-light border-b-2 bg-slate-50 border-slate-400 text-lg focus:outline-none focus:border-blue-500 transition-colors duration-200"
|
||||
/>
|
||||
</section>
|
||||
<section className="flex flex-col">
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="font-medium text-slate-600 -mb-2 z-10 text-lg">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id={opts.passAutoComplete}
|
||||
name="password"
|
||||
autoComplete={opts.passAutoComplete}
|
||||
aria-describedby={
|
||||
action === "login" ? undefined : "password-constraints"
|
||||
}
|
||||
type="password"
|
||||
placeholder=" "
|
||||
onChange={onPasswordChange}
|
||||
minLength={8}
|
||||
maxLength={128}
|
||||
required
|
||||
className="mb-1 p-2 border-b-2 bg-slate-50 border-slate-400 text-lg focus:outline-none focus:border-blue-500 transition-colors duration-200"
|
||||
/>
|
||||
{serverConfig.disableCredentialLogin ? null : (
|
||||
<Form method="post" replace className="flex flex-col gap-y-2">
|
||||
{actionData && actionData.error ? (
|
||||
<p className="text-red-500 text-center font-medium">
|
||||
{actionData.error}
|
||||
</p>
|
||||
) : null}
|
||||
<input type="hidden" name="redirectTo" value={from} />
|
||||
<section className="flex flex-col">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="font-medium text-slate-600 -mb-2 z-10 text-lg">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
autoFocus
|
||||
id="email"
|
||||
name="username"
|
||||
autoComplete="username"
|
||||
type="email"
|
||||
required
|
||||
placeholder=" "
|
||||
minLength={4}
|
||||
maxLength={128}
|
||||
className="p-1 font-light border-b-2 bg-slate-50 border-slate-400 text-lg focus:outline-none focus:border-blue-500 transition-colors duration-200"
|
||||
/>
|
||||
</section>
|
||||
<section className="flex flex-col">
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="font-medium text-slate-600 -mb-2 z-10 text-lg">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id={opts.passAutoComplete}
|
||||
name="password"
|
||||
autoComplete={opts.passAutoComplete}
|
||||
aria-describedby={
|
||||
action === "login" ? undefined : "password-constraints"
|
||||
}
|
||||
type="password"
|
||||
placeholder=" "
|
||||
onChange={onPasswordChange}
|
||||
minLength={8}
|
||||
maxLength={128}
|
||||
required
|
||||
className="mb-1 p-2 border-b-2 bg-slate-50 border-slate-400 text-lg focus:outline-none focus:border-blue-500 transition-colors duration-200"
|
||||
/>
|
||||
|
||||
{action === "login" ? null : (
|
||||
<>
|
||||
<PassScore score={passwordScore} />
|
||||
<div
|
||||
id="password-constraints"
|
||||
className="mt-2 text-sm text-slate-500">
|
||||
Password must be:
|
||||
<ul className="list-disc list-inside">
|
||||
<li className="list-item">At least 8 characters long</li>
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
<Button
|
||||
id={opts.buttonID}
|
||||
type="submit"
|
||||
color="blue"
|
||||
disabled={isLoading || !canSubmit}
|
||||
className="mt-6 px-8 py-3 max-w-fit mx-auto">
|
||||
{isLoading ? opts.buttonLoadingText : opts.buttonText}
|
||||
</Button>
|
||||
<span className="text-slate-500 font-light text-center text-sm">
|
||||
{opts.altLabel + " "}
|
||||
<Link
|
||||
className="text-blue-500 font-medium transition-colors duration-200 hover:text-blue-600"
|
||||
to={altLink}>
|
||||
{opts.altLinkText}
|
||||
</Link>
|
||||
</span>
|
||||
</Form>
|
||||
{action === "login" ? null : (
|
||||
<>
|
||||
<PassScore score={passwordScore} />
|
||||
<div
|
||||
id="password-constraints"
|
||||
className="mt-2 text-sm text-slate-500">
|
||||
Password must be:
|
||||
<ul className="list-disc list-inside">
|
||||
<li className="list-item">At least 8 characters long</li>
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
<Button
|
||||
id={opts.buttonID}
|
||||
type="submit"
|
||||
color="blue"
|
||||
disabled={isLoading || !canSubmit}
|
||||
className="mt-6 px-8 py-3 max-w-fit mx-auto">
|
||||
{isLoading ? opts.buttonLoadingText : opts.buttonText}
|
||||
</Button>
|
||||
</Form>
|
||||
)}
|
||||
{serverConfig?.oidcIssuerUrl.length && (
|
||||
<a href={`${import.meta.env.VITE_OIDC_URL}/redirect`}>
|
||||
<Button color="green" className="px-8 py-3 max-w-fit mx-auto">
|
||||
{`${opts.buttonText} with ${serverConfig.oidcIssuerName}`}
|
||||
</Button>
|
||||
</a>
|
||||
)}
|
||||
{serverConfig.disableRegistration && action === "login" ? null : (
|
||||
<span className="text-slate-500 font-light text-center text-sm">
|
||||
{opts.altLabel + " "}
|
||||
<Link
|
||||
className="text-blue-500 font-medium transition-colors duration-200 hover:text-blue-600"
|
||||
to={altLink}>
|
||||
{opts.altLinkText}
|
||||
</Link>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import {
|
||||
createContext,
|
||||
useState,
|
||||
useEffect,
|
||||
useContext,
|
||||
FC,
|
||||
ReactNode,
|
||||
} from "react"
|
||||
|
||||
import fetchAPI from "../util/fetchAPI"
|
||||
|
||||
// These configs mirror their server-side counterparts
|
||||
|
||||
export type ServerConfig = {
|
||||
disableRegistration: boolean
|
||||
disableCredentialLogin: boolean
|
||||
oidcIssuerUrl: string
|
||||
oidcIssuerName: string
|
||||
}
|
||||
|
||||
const defaultConfig: ServerConfig = {
|
||||
disableRegistration: false,
|
||||
disableCredentialLogin: false,
|
||||
oidcIssuerUrl: "",
|
||||
oidcIssuerName: "",
|
||||
}
|
||||
|
||||
const serverContext = createContext<ServerConfig>(defaultConfig)
|
||||
|
||||
export const useServerConfig = () => {
|
||||
return useContext(serverContext)
|
||||
}
|
||||
|
||||
export const ServerConfigProvider: FC<{
|
||||
children?: ReactNode
|
||||
}> = ({ children }) => {
|
||||
const [config, setConfig] = useState<ServerConfig>(defaultConfig)
|
||||
|
||||
useEffect(() => {
|
||||
fetchAPI<ServerConfig>("/config").then((result) => {
|
||||
if (result.ok) {
|
||||
setConfig(result.data)
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<serverContext.Provider value={config}>{children}</serverContext.Provider>
|
||||
)
|
||||
}
|
|
@ -3,6 +3,7 @@ import { StrictMode } from "react"
|
|||
import { createRoot } from "react-dom/client"
|
||||
import { RouterProvider } from "react-router-dom"
|
||||
|
||||
import { ServerConfigProvider } from "./hooks/useServerConfig.tsx"
|
||||
import "./index.css"
|
||||
import router from "./router.tsx"
|
||||
|
||||
|
@ -12,6 +13,8 @@ if (!rootEl) throw new Error("Root element not found")
|
|||
|
||||
createRoot(rootEl).render(
|
||||
<StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
<ServerConfigProvider>
|
||||
<RouterProvider router={router} />
|
||||
</ServerConfigProvider>
|
||||
</StrictMode>
|
||||
)
|
||||
|
|
|
@ -56,6 +56,8 @@ const (
|
|||
DefaultQuiet = false
|
||||
// DefaultDisableCredentialsLogin is the default value for diable credential login.
|
||||
DefaultDisableCredentialsLogin = false
|
||||
// DefaultOIDCIssuerName is the default name of the OIDC issuer.
|
||||
DefaultOIDCIssuerName = "OpenID"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -103,7 +105,9 @@ type Config struct {
|
|||
// DisableCredentialsLogin defines whether or not the server should disable credential login.
|
||||
DisableCredentialsLogin bool `json:"disableCredentialLogin"`
|
||||
// OIDCIssuerURL is the URL of the OIDC issuer.
|
||||
OIDCIssuerURL string
|
||||
OIDCIssuerURL string `json:"oidcIssuerUrl"`
|
||||
// OIDCIssueName is the name of the OIDC issuer.
|
||||
OIDCIssuerName string
|
||||
// OIDCClientID is the client ID for OIDC.
|
||||
OIDCClientID string
|
||||
// OIDCClientSecret is the client secret for OIDC.
|
||||
|
@ -126,6 +130,7 @@ func NewConfig() *Config {
|
|||
Verbose: DefaultVerbose,
|
||||
Quiet: DefaultQuiet,
|
||||
DisableCredentialsLogin: DefaultDisableCredentialsLogin,
|
||||
OIDCIssuerName: DefaultOIDCIssuerName,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"git.maronato.dev/maronato/goshort/internal/errs"
|
||||
"git.maronato.dev/maronato/goshort/internal/server"
|
||||
authmiddleware "git.maronato.dev/maronato/goshort/internal/server/middleware/auth"
|
||||
configservice "git.maronato.dev/maronato/goshort/internal/service/config"
|
||||
shortservice "git.maronato.dev/maronato/goshort/internal/service/short"
|
||||
shortlogservice "git.maronato.dev/maronato/goshort/internal/service/shortlog"
|
||||
tokenservice "git.maronato.dev/maronato/goshort/internal/service/token"
|
||||
|
@ -27,6 +28,7 @@ type APIHandler struct {
|
|||
users *userservice.UserService
|
||||
tokens *tokenservice.TokenService
|
||||
shortLogs *shortlogservice.ShortLogService
|
||||
config *configservice.ConfigService
|
||||
}
|
||||
|
||||
func NewAPIHandler(
|
||||
|
@ -34,12 +36,14 @@ func NewAPIHandler(
|
|||
users *userservice.UserService,
|
||||
tokens *tokenservice.TokenService,
|
||||
shortLogs *shortlogservice.ShortLogService,
|
||||
config *configservice.ConfigService,
|
||||
) *APIHandler {
|
||||
return &APIHandler{
|
||||
shorts: shorts,
|
||||
users: users,
|
||||
tokens: tokens,
|
||||
shortLogs: shortLogs,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,6 +175,14 @@ func (h *APIHandler) Signup(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
l := logging.FromCtx(ctx)
|
||||
|
||||
if h.config.GetPublicConfig().DisableCredentialsLogin {
|
||||
l.Debug("credentials registration is disabled")
|
||||
|
||||
server.RenderForbidden(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Get the user from the json body
|
||||
type signupForm struct {
|
||||
models.User
|
||||
|
@ -578,6 +590,21 @@ func (h *APIHandler) DeleteToken(w http.ResponseWriter, r *http.Request) {
|
|||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
// PublicConfig returns the public configuration of the server.
|
||||
func (h *APIHandler) PublicConfig(w http.ResponseWriter, r *http.Request) {
|
||||
_, span := tracing.StartSpan(r.Context(), "api.PublicConfig")
|
||||
defer span.End()
|
||||
|
||||
// Get public config
|
||||
config := h.config.GetPublicConfig()
|
||||
|
||||
span.AddEvent("got public config")
|
||||
|
||||
// Render the response
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, config)
|
||||
}
|
||||
|
||||
// findUserOrRespond is a helper function that finds a user in the session,
|
||||
// and returns it. If the user is not found, it returns nil and false.
|
||||
func (h *APIHandler) findUserOrRespond(w http.ResponseWriter, r *http.Request) (user *models.User, ok bool) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
apiserver "git.maronato.dev/maronato/goshort/internal/server/api"
|
||||
servermiddleware "git.maronato.dev/maronato/goshort/internal/server/middleware"
|
||||
authmiddleware "git.maronato.dev/maronato/goshort/internal/server/middleware/auth"
|
||||
configservice "git.maronato.dev/maronato/goshort/internal/service/config"
|
||||
shortservice "git.maronato.dev/maronato/goshort/internal/service/short"
|
||||
shortlogservice "git.maronato.dev/maronato/goshort/internal/service/shortlog"
|
||||
tokenservice "git.maronato.dev/maronato/goshort/internal/service/token"
|
||||
|
@ -62,8 +63,9 @@ func setup(ctx context.Context, cfg *config.Config) (
|
|||
users := userservice.NewUserService(cfg, str)
|
||||
tokens := tokenservice.NewTokenService(str)
|
||||
shortLogs := shortlogservice.NewShortLogService(str)
|
||||
cfgService := configservice.NewConfigService(cfg)
|
||||
|
||||
api := apiserver.NewAPIHandler(shorts, users, tokens, shortLogs)
|
||||
api := apiserver.NewAPIHandler(shorts, users, tokens, shortLogs, cfgService)
|
||||
|
||||
return api, shorts, users, tokens, shortLogs
|
||||
}
|
||||
|
|
|
@ -15,6 +15,9 @@ func NewAPIRouter(handler *APIHandler) http.Handler {
|
|||
mux.Post("/logout", handler.Logout)
|
||||
mux.Post("/signup", handler.Signup)
|
||||
|
||||
// Public routes
|
||||
mux.Get("/config", handler.PublicConfig)
|
||||
|
||||
// Authenticated routes
|
||||
mux.Group(func(r chi.Router) {
|
||||
// UI and API endpoints
|
||||
|
|
|
@ -18,6 +18,8 @@ import (
|
|||
type Server struct {
|
||||
// apiUrl is API server's URL
|
||||
apiURL string
|
||||
// oidcURL is OIDC server's URL
|
||||
oidcURL string
|
||||
// uiPort is the port the UI server will listen on
|
||||
uiPort string
|
||||
// host is the host the UI server will listen on
|
||||
|
@ -33,12 +35,18 @@ func NewServer(cfg *config.Config) *Server {
|
|||
Scheme: "http",
|
||||
Path: "/api",
|
||||
}
|
||||
oidcURL := url.URL{
|
||||
Host: net.JoinHostPort(cfg.Host, cfg.Port),
|
||||
Scheme: "http",
|
||||
Path: "/oidc",
|
||||
}
|
||||
|
||||
return &Server{
|
||||
apiURL: apiURL.String(),
|
||||
uiPort: cfg.UIPort,
|
||||
host: cfg.Host,
|
||||
cancel: func() {},
|
||||
apiURL: apiURL.String(),
|
||||
oidcURL: oidcURL.String(),
|
||||
uiPort: cfg.UIPort,
|
||||
host: cfg.Host,
|
||||
cancel: func() {},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +94,7 @@ func (s *Server) Start(ctx context.Context) error {
|
|||
}
|
||||
|
||||
// Set the API_URL env var
|
||||
cmd.Env = append(os.Environ(), "VITE_API_URL="+s.apiURL)
|
||||
cmd.Env = append(os.Environ(), "VITE_API_URL="+s.apiURL, "VITE_OIDC_URL="+s.oidcURL)
|
||||
|
||||
// Use the current process's stdout
|
||||
cmd.Stdout = os.Stdout
|
||||
|
|
|
@ -2,8 +2,11 @@ package oidcserver
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"git.maronato.dev/maronato/goshort/internal/config"
|
||||
"git.maronato.dev/maronato/goshort/internal/errs"
|
||||
"git.maronato.dev/maronato/goshort/internal/server"
|
||||
authmiddleware "git.maronato.dev/maronato/goshort/internal/server/middleware/auth"
|
||||
|
@ -17,14 +20,18 @@ import (
|
|||
)
|
||||
|
||||
type OIDCHandler struct {
|
||||
oidc *oidcservice.OIDCService
|
||||
user *userservice.UserService
|
||||
oidc *oidcservice.OIDCService
|
||||
user *userservice.UserService
|
||||
uiHost string
|
||||
}
|
||||
|
||||
func NewOIDCHandler(oidc *oidcservice.OIDCService, user *userservice.UserService) *OIDCHandler {
|
||||
func NewOIDCHandler(cfg *config.Config, oidc *oidcservice.OIDCService, user *userservice.UserService) *OIDCHandler {
|
||||
uiHost := net.JoinHostPort(cfg.Host, cfg.UIPort)
|
||||
|
||||
return &OIDCHandler{
|
||||
oidc: oidc,
|
||||
user: user,
|
||||
oidc: oidc,
|
||||
user: user,
|
||||
uiHost: uiHost,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,7 +87,6 @@ func (h *OIDCHandler) Callback(w http.ResponseWriter, r *http.Request) {
|
|||
user.SetNoLoginPassword()
|
||||
|
||||
user, err = h.user.CreateUser(ctx, user)
|
||||
|
||||
// Handle errors
|
||||
if err != nil {
|
||||
switch {
|
||||
|
@ -115,5 +121,10 @@ func (h *OIDCHandler) Callback(w http.ResponseWriter, r *http.Request) {
|
|||
span.AddEvent("logged in user")
|
||||
|
||||
// Redirect to the home page
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
uiURL := url.URL{
|
||||
Scheme: r.URL.Scheme,
|
||||
Host: h.uiHost,
|
||||
Path: "/",
|
||||
}
|
||||
http.Redirect(w, r, uiURL.String(), http.StatusFound)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package configservice
|
||||
|
||||
import "git.maronato.dev/maronato/goshort/internal/config"
|
||||
|
||||
type PublicConfig struct {
|
||||
// DisableRegistration defines whether or not registration are disabled.
|
||||
DisableRegistration bool `json:"disableRegistration"`
|
||||
// DisableCredentialsLogin defines whether or not the server should disable credential login.
|
||||
DisableCredentialsLogin bool `json:"disableCredentialLogin"`
|
||||
// OIDCIssuerURL is the URL of the OIDC issuer.
|
||||
OIDCIssuerURL string `json:"oidcIssuerUrl"`
|
||||
// OIDCIssuerName is the name of the OIDC issuer.
|
||||
OIDCIssuerName string `json:"oidcIssuerName"`
|
||||
}
|
||||
|
||||
// ConfigService is the service that handles the configuration.
|
||||
type ConfigService struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
// NewConfigService creates a new configuration service.
|
||||
func NewConfigService(cfg *config.Config) *ConfigService {
|
||||
return &ConfigService{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPublicConfigJSON returns the public configuration as a JSON string.
|
||||
func (s *ConfigService) GetPublicConfig() *PublicConfig {
|
||||
return &PublicConfig{
|
||||
DisableRegistration: s.cfg.DisableRegistration,
|
||||
DisableCredentialsLogin: s.cfg.DisableCredentialsLogin,
|
||||
OIDCIssuerURL: s.cfg.OIDCIssuerURL,
|
||||
OIDCIssuerName: s.cfg.OIDCIssuerName,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user