mirror of
https://github.com/penpot/penpot.git
synced 2026-05-24 09:23:40 +00:00
194 lines
6.9 KiB
Clojure
194 lines
6.9 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS INC
|
|
|
|
(ns app.rpc.commands.verify-token
|
|
(:require
|
|
[app.common.exceptions :as ex]
|
|
[app.common.spec :as us]
|
|
[app.db :as db]
|
|
[app.loggers.audit :as audit]
|
|
[app.rpc.doc :as-alias doc]
|
|
[app.rpc.mutations.teams :as teams]
|
|
[app.rpc.queries.profile :as profile]
|
|
[app.tokens :as tokens]
|
|
[app.tokens.spec.team-invitation :as-alias spec.team-invitation]
|
|
[app.util.services :as sv]
|
|
[clojure.spec.alpha :as s]))
|
|
|
|
(s/def ::iss keyword?)
|
|
(s/def ::exp ::us/inst)
|
|
|
|
(defmulti process-token (fn [_ _ claims] (:iss claims)))
|
|
|
|
(s/def ::verify-token
|
|
(s/keys :req-un [::token]
|
|
:opt-un [::profile-id]))
|
|
|
|
(sv/defmethod ::verify-token
|
|
{:auth false
|
|
::doc/added "1.15"}
|
|
[{:keys [pool sprops] :as cfg} {:keys [token] :as params}]
|
|
(db/with-atomic [conn pool]
|
|
(let [claims (tokens/verify sprops {:token token})
|
|
cfg (assoc cfg :conn conn)]
|
|
(process-token cfg params claims))))
|
|
|
|
(defmethod process-token :change-email
|
|
[{:keys [conn] :as cfg} _params {:keys [profile-id email] :as claims}]
|
|
(when (profile/retrieve-profile-data-by-email conn email)
|
|
(ex/raise :type :validation
|
|
:code :email-already-exists))
|
|
|
|
(db/update! conn :profile
|
|
{:email email}
|
|
{:id profile-id})
|
|
|
|
(with-meta claims
|
|
{::audit/name "update-profile-email"
|
|
::audit/props {:email email}
|
|
::audit/profile-id profile-id}))
|
|
|
|
(defmethod process-token :verify-email
|
|
[{:keys [conn session] :as cfg} _ {:keys [profile-id] :as claims}]
|
|
(let [profile (profile/retrieve-profile conn profile-id)
|
|
claims (assoc claims :profile profile)]
|
|
|
|
(when-not (:is-active profile)
|
|
(when (not= (:email profile)
|
|
(:email claims))
|
|
(ex/raise :type :validation
|
|
:code :invalid-token))
|
|
|
|
(db/update! conn :profile
|
|
{:is-active true}
|
|
{:id (:id profile)}))
|
|
|
|
(with-meta claims
|
|
{:transform-response ((:create session) profile-id)
|
|
::audit/name "verify-profile-email"
|
|
::audit/props (audit/profile->props profile)
|
|
::audit/profile-id (:id profile)})))
|
|
|
|
(defmethod process-token :auth
|
|
[{:keys [conn] :as cfg} _params {:keys [profile-id] :as claims}]
|
|
(let [profile (profile/retrieve-profile conn profile-id)]
|
|
(assoc claims :profile profile)))
|
|
|
|
;; --- Team Invitation
|
|
|
|
(defn- accept-invitation
|
|
[{:keys [conn] :as cfg} {:keys [team-id role member-email] :as claims} invitation member]
|
|
(let [;; Update the role if there is an invitation
|
|
role (or (some-> invitation :role keyword) role)
|
|
params (merge
|
|
{:team-id team-id
|
|
:profile-id (:id member)}
|
|
(teams/role->params role))]
|
|
|
|
;; Do not allow blocked users accept invitations.
|
|
(when (:is-blocked member)
|
|
(ex/raise :type :restriction
|
|
:code :profile-blocked))
|
|
|
|
;; Insert the invited member to the team
|
|
(db/insert! conn :team-profile-rel params {:on-conflict-do-nothing true})
|
|
|
|
;; If profile is not yet verified, mark it as verified because
|
|
;; accepting an invitation link serves as verification.
|
|
(when-not (:is-active member)
|
|
(db/update! conn :profile
|
|
{:is-active true}
|
|
{:id (:id member)}))
|
|
|
|
;; Delete the invitation
|
|
(db/delete! conn :team-invitation
|
|
{:team-id team-id :email-to member-email})
|
|
|
|
(assoc member :is-active true)))
|
|
|
|
(s/def ::spec.team-invitation/profile-id ::us/uuid)
|
|
(s/def ::spec.team-invitation/role ::us/keyword)
|
|
(s/def ::spec.team-invitation/team-id ::us/uuid)
|
|
(s/def ::spec.team-invitation/member-email ::us/email)
|
|
(s/def ::spec.team-invitation/member-id (s/nilable ::us/uuid))
|
|
|
|
(s/def ::team-invitation-claims
|
|
(s/keys :req-un [::iss ::exp
|
|
::spec.team-invitation/profile-id
|
|
::spec.team-invitation/role
|
|
::spec.team-invitation/team-id
|
|
::spec.team-invitation/member-email]
|
|
:opt-un [::spec.team-invitation/member-id]))
|
|
|
|
(defmethod process-token :team-invitation
|
|
[{:keys [conn session] :as cfg} {:keys [profile-id token]}
|
|
{:keys [member-id team-id member-email] :as claims}]
|
|
|
|
(us/assert ::team-invitation-claims claims)
|
|
|
|
(let [invitation (db/get* conn :team-invitation
|
|
{:team-id team-id :email-to member-email})
|
|
profile (db/get* conn :profile
|
|
{:id profile-id}
|
|
{:columns [:id :email]})]
|
|
(when (nil? invitation)
|
|
(ex/raise :type :validation
|
|
:code :invalid-token
|
|
:hint "no invitation associated with the token"))
|
|
|
|
(if (some? profile)
|
|
(if (or (= member-id profile-id)
|
|
(= member-email (:email profile)))
|
|
;; if we have logged-in user and it matches the invitation we
|
|
;; proceed with accepting the invitation and joining the
|
|
;; current profile to the invited team.
|
|
(let [profile (accept-invitation cfg claims invitation profile)]
|
|
(with-meta
|
|
(assoc claims :state :created)
|
|
{::audit/name "accept-team-invitation"
|
|
::audit/props (merge
|
|
(audit/profile->props profile)
|
|
{:team-id (:team-id claims)
|
|
:role (:role claims)})
|
|
::audit/profile-id profile-id}))
|
|
|
|
(ex/raise :type :validation
|
|
:code :invalid-token
|
|
:hint "logged-in user does not matches the invitation"))
|
|
|
|
;; If we have not logged-in user, we try find the invited
|
|
;; profile by member-id or member-email props of the invitation
|
|
;; token; If profile is found, we accept the invitation and
|
|
;; leave the user logged-in.
|
|
(if-let [member (db/get* conn :profile
|
|
(if member-id
|
|
{:id member-id}
|
|
{:email member-email})
|
|
{:columns [:id :email]})]
|
|
(let [profile (accept-invitation cfg claims invitation member)]
|
|
(with-meta
|
|
(assoc claims :state :created)
|
|
{:transform-response ((:create session) (:id profile))
|
|
::audit/name "accept-team-invitation"
|
|
::audit/props (merge
|
|
(audit/profile->props profile)
|
|
{:team-id (:team-id claims)
|
|
:role (:role claims)})
|
|
::audit/profile-id member-id}))
|
|
|
|
{:invitation-token token
|
|
:iss :team-invitation
|
|
:redirect-to :auth-register
|
|
:state :pending}))))
|
|
|
|
;; --- Default
|
|
|
|
(defmethod process-token :default
|
|
[_ _ _]
|
|
(ex/raise :type :validation
|
|
:code :invalid-token))
|
|
|