diff --git a/cmd/auth.go b/cmd/auth.go index e99e3d0..3320378 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -1,6 +1,8 @@ package main import ( + "strconv" + "github.com/abhinavxd/artemis/internal/envelope" "github.com/abhinavxd/artemis/internal/stringutil" @@ -8,35 +10,52 @@ import ( "github.com/zerodha/fastglue" ) -// handleOIDCLogin initializes an OIDC request and redirects to the OIDC provider for login. +// handleOIDCLogin redirects to the OIDC provider for login. func handleOIDCLogin(r *fastglue.Request) error { var ( - app = r.Context.(*App) + app = r.Context.(*App) + providerID, err = strconv.Atoi(r.RequestCtx.UserValue("id").(string)) ) + if err != nil { + app.lo.Error("error parsing provider id", "error", err) + return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error parsing provider id.", nil, envelope.GeneralError) + } + + // TODO: Figure csrf thing out state, err := stringutil.RandomAlNumString(30) if err != nil { app.lo.Error("error generating random string", "error", err) return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Something went wrong, Please try again.", nil, envelope.GeneralError) } - authURL := app.auth.LoginURL(state) + + authURL, err := app.auth.LoginURL(providerID, state) + if err != nil { + return sendErrorEnvelope(r, err) + } + return r.Redirect(authURL, fasthttp.StatusFound, nil, "") } // handleOIDCCallback receives the redirect callback from the OIDC provider and completes the handshake. func handleOIDCCallback(r *fastglue.Request) error { var ( - app = r.Context.(*App) - code = string(r.RequestCtx.QueryArgs().Peek("code")) - state = string(r.RequestCtx.QueryArgs().Peek("state")) + app = r.Context.(*App) + code = string(r.RequestCtx.QueryArgs().Peek("code")) + state = string(r.RequestCtx.QueryArgs().Peek("state")) + providerID, err = strconv.Atoi(string(r.RequestCtx.QueryArgs().Peek("id"))) ) + if err != nil { + app.lo.Error("error parsing provider id", "error", err) + return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error parsing provider id.", nil, envelope.GeneralError) + } - _, claims, err := app.auth.ExchangeOIDCToken(r.RequestCtx, code) + _, claims, err := app.auth.ExchangeOIDCToken(r.RequestCtx, providerID, code) if err != nil { app.lo.Error("error exchanging oidc token", "error", err) return err } - // Get user by e-mail received from OIDC. + // Get user by e-mail received. user, err := app.user.GetByEmail(claims.Email) if err != nil { return err diff --git a/cmd/handlers.go b/cmd/handlers.go index ab56662..c2c3e39 100644 --- a/cmd/handlers.go +++ b/cmd/handlers.go @@ -14,7 +14,7 @@ import ( func initHandlers(g *fastglue.Fastglue, hub *ws.Hub) { g.POST("/api/login", handleLogin) g.GET("/api/logout", handleLogout) - g.GET("/api/oidc/login", handleOIDCLogin) + g.GET("/api/oidc/{id}/login", handleOIDCLogin) g.GET("/api/oidc/finish", handleOIDCCallback) // Health check. diff --git a/cmd/init.go b/cmd/init.go index 16bd5a1..e63c420 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -269,7 +269,7 @@ func initMedia(db *sqlx.DB) *media.Manager { case "localfs": store, err = localfs.New(localfs.Opts{ UploadURI: "/uploads", - UploadPath: filepath.Clean(ko.String("app.localfs.upload_path")), + UploadPath: filepath.Clean(ko.String("upload.localfs.upload_path")), RootURL: ko.String("app.root_url"), }) if err != nil { @@ -411,16 +411,31 @@ func registerInboxes(mgr *inbox.Manager, store inbox.MessageStore) { } } -func initAuth(rd *redis.Client) *auth.Auth { - lo := initLogger("auth") +func initAuth(o *oidc.Manager, rd *redis.Client) *auth.Auth { + var lo = initLogger("auth") + + oidc, err := o.GetAll() + if err != nil { + log.Fatalf("error initializing auth: %v", err) + } + + var providers = make([]auth.Provider, 0, len(oidc)) + for _, o := range oidc { + if o.Disabled { + continue + } + providers = append(providers, auth.Provider{ + ID: o.ID, + Provider: o.Provider, + ProviderURL: o.ProviderURL, + RedirectURL: o.RedirectURI, + ClientID: o.ClientID, + ClientSecret: o.ClientSecret, + }) + } + a, err := auth.New(auth.Config{ - OIDC: auth.OIDCConfig{ - Enabled: true, - ProviderURL: "https://accounts.google.com", - RedirectURL: "http://localhost:5173/auth/oidc/finish", - ClientID: "a", - ClientSecret: "a", - }, + Providers: providers, }, rd, lo) if err != nil { log.Fatalf("error initializing auth: %v", err) diff --git a/cmd/main.go b/cmd/main.go index e16b67a..bfea5ad 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -83,6 +83,8 @@ func main() { i18n = initI18n(fs) lo = initLogger("artemis") rdb = initRedis() + oidc = initOIDC(db) + auth = initAuth(oidc, rdb) template = initTemplate(db) media = initMedia(db) contact = initContact(db) @@ -119,7 +121,7 @@ func main() { var app = &App{ lo: lo, rdb: rdb, - auth: initAuth(rdb), + auth: auth, fs: fs, i18n: i18n, media: media, @@ -131,7 +133,7 @@ func main() { tmpl: template, conversation: conversation, automation: automation, - oidc: initOIDC(db), + oidc: oidc, role: initRole(db), constant: initConstants(), tag: initTags(db), diff --git a/cmd/oidc.go b/cmd/oidc.go index bb47a3a..ecf9f57 100644 --- a/cmd/oidc.go +++ b/cmd/oidc.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "strconv" "github.com/abhinavxd/artemis/internal/envelope" @@ -9,32 +10,38 @@ import ( "github.com/zerodha/fastglue" ) -// handleGetAllOIDC returns all oidc records +const ( + redirectURI = "/api/oidc/finish?id=%d" +) + +// handleGetAllOIDC returns all OIDC records func handleGetAllOIDC(r *fastglue.Request) error { - var ( - app = r.Context.(*App) - ) - o, err := app.oidc.GetAll() + app := r.Context.(*App) + + out, err := app.oidc.GetAll() if err != nil { return sendErrorEnvelope(r, err) } - return r.SendEnvelope(o) + return r.SendEnvelope(out) } // handleGetOIDC returns an OIDC record by id. func handleGetOIDC(r *fastglue.Request) error { - var ( - app = r.Context.(*App) - ) + app := r.Context.(*App) + id, err := strconv.Atoi(r.RequestCtx.UserValue("id").(string)) - if err != nil || id == 0 { + if err != nil || id <= 0 { return r.SendErrorEnvelope(fasthttp.StatusBadRequest, - "Invalid oidc `id`", nil, envelope.InputError) + "Invalid OIDC `id`", nil, envelope.InputError) } + o, err := app.oidc.Get(id) if err != nil { return sendErrorEnvelope(r, err) } + + o.RedirectURI = fmt.Sprintf("%s%s", app.constant.AppBaseURL, fmt.Sprintf(redirectURI, o.ID)) + return r.SendEnvelope(o) } diff --git a/frontend/package.json b/frontend/package.json index cd3d8a5..3eacbe5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -54,7 +54,7 @@ "textarea": "^0.3.0", "tiptap-extension-resize-image": "^1.1.5", "vee-validate": "^4.13.2", - "vue": "^3.4.15", + "vue": "^3.4.37", "vue-draggable-resizable": "^3.0.0", "vue-i18n": "9", "vue-letter": "^0.2.0", diff --git a/frontend/public/images/github-logo.png b/frontend/public/images/github-logo.png new file mode 100644 index 0000000..a463869 Binary files /dev/null and b/frontend/public/images/github-logo.png differ diff --git a/frontend/public/images/google-logo.png b/frontend/public/images/google-logo.png new file mode 100644 index 0000000..a4a9918 Binary files /dev/null and b/frontend/public/images/google-logo.png differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 6b0665b..7fe2be3 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -7,7 +7,7 @@ - + @@ -70,6 +70,16 @@ const allNavLinks = ref([ permission: 'admin:get', }, ]); + +const bottomLinks = ref( + [ + { + to: '/logout', + icon: 'lucide:log-out', + title: 'Logout' + } + ] +) const userStore = useUserStore(); const router = useRouter(); diff --git a/frontend/src/components/NavBar.vue b/frontend/src/components/NavBar.vue index 2ea5ce1..811ce56 100644 --- a/frontend/src/components/NavBar.vue +++ b/frontend/src/components/NavBar.vue @@ -8,7 +8,8 @@ import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@/comp defineProps({ isCollapsed: Boolean, - links: Array + links: Array, + bottomLinks: Array }) const route = useRoute() @@ -25,7 +26,7 @@ const getButtonVariant = (to) => { diff --git a/frontend/src/components/account/ProfileEdit.vue b/frontend/src/components/account/ProfileEdit.vue index bf93d3b..4a8bb1b 100644 --- a/frontend/src/components/account/ProfileEdit.vue +++ b/frontend/src/components/account/ProfileEdit.vue @@ -1,7 +1,7 @@