diff --git a/backend/app/api/handlers/v1/v1_ctrl_auth.go b/backend/app/api/handlers/v1/v1_ctrl_auth.go index 936f38e..22b0e2a 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_auth.go +++ b/backend/app/api/handlers/v1/v1_ctrl_auth.go @@ -89,6 +89,28 @@ func (ctrl *V1Controller) HandleAuthLogin() errchain.HandlerFunc { } } +func (ctrl *V1Controller) HandleSsoHeaderLogin() server.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) error { + var username = r.Header.Get("Remote-Email") + + if username == "" { + return validate.NewRequestError(errors.New("authentication failed. not SSO header found"), http.StatusInternalServerError) + } + + newToken, err := ctrl.svc.User.LoginWithoutPassword(r.Context(), strings.ToLower(username)) + + if err != nil { + return validate.NewRequestError(errors.New("authentication failed"), http.StatusInternalServerError) + } + + return server.Respond(w, http.StatusOK, TokenResponse{ + Token: "Bearer " + newToken.Raw, + ExpiresAt: newToken.ExpiresAt, + AttachmentToken: newToken.AttachmentToken, + }) + } +} + // HandleAuthLogout godoc // // @Summary User Logout diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index f27069e..f62400e 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -64,6 +64,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR r.Post(v1Base("/users/register"), chain.ToHandlerFunc(v1Ctrl.HandleUserRegistration())) r.Post(v1Base("/users/login"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin())) + r.server.Post(v1Base("/users/login-sso-header"), v1Ctrl.HandleSsoHeaderLogin()) userMW := []errchain.Middleware{ a.mwAuthToken, diff --git a/backend/internal/core/services/service_user.go b/backend/internal/core/services/service_user.go index 52da946..da49f20 100644 --- a/backend/internal/core/services/service_user.go +++ b/backend/internal/core/services/service_user.go @@ -193,6 +193,18 @@ func (svc *UserService) Login(ctx context.Context, username, password string, ex return svc.createSessionToken(ctx, usr.ID, extendedSession) } +func (svc *UserService) LoginWithoutPassword(ctx context.Context, username string) (UserAuthTokenDetail, error) { + usr, err := svc.repos.Users.GetOneEmail(ctx, username) + + if err != nil { + // SECURITY: Perform hash to ensure response times are the same + hasher.CheckPasswordHash("not-a-real-password", "not-a-real-password") + return UserAuthTokenDetail{}, ErrorInvalidLogin + } + + return svc.createSessionToken(ctx, usr.ID) +} + func (svc *UserService) Logout(ctx context.Context, token string) error { hash := hasher.HashToken(token) err := svc.repos.AuthTokens.DeleteToken(ctx, hash) diff --git a/frontend/lib/api/public.ts b/frontend/lib/api/public.ts index ae8735a..2a52ce4 100644 --- a/frontend/lib/api/public.ts +++ b/frontend/lib/api/public.ts @@ -24,6 +24,14 @@ export class PublicApi extends BaseAPI { }); } + public login_sso_header() { + return this.http.post({ + url: route("/users/login-sso-header"), + /** TODO: remove header here. Only for testing. Usually the SSO servie will add this */ + headers: { "Remote-Email": "demo@example.com" }, + }); + } + public register(body: UserRegistration) { return this.http.post({ url: route("/users/register"), body }); } diff --git a/frontend/pages/index.vue b/frontend/pages/index.vue index 2258c4c..edecfd9 100644 --- a/frontend/pages/index.vue +++ b/frontend/pages/index.vue @@ -32,6 +32,19 @@ } }); + const { data, error } = await api.login_sso_header(); + + if (!error) { + // @ts-expect-error - expires is either a date or a string, need to figure out store typing + authStore.$patch({ + token: data.token, + expires: data.expiresAt, + attachmentToken: data.attachmentToken, + }); + + navigateTo("/home"); + } + const route = useRoute(); const router = useRouter();