Skip to content

Commit

Permalink
password reset
Browse files Browse the repository at this point in the history
  • Loading branch information
johannesloetzsch committed May 3, 2023
1 parent f504fa3 commit 62ad93e
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 18 deletions.
4 changes: 3 additions & 1 deletion backend/src/swlkup/resolver/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
[swlkup.resolver.root.ngo.create-token :refer [create_token]]
[swlkup.resolver.root.ngo.created-tokens :refer [created_tokens]]
[swlkup.resolver.root.ngo.invalidate-token :refer [invalidate_token]]
[swlkup.resolver.root.ngo.supervisor-password-reset :refer [supervisor_password_reset]]
;; admin passphrase
[swlkup.resolver.root.admin.export :refer [export]]))

Expand All @@ -39,7 +40,8 @@
:supervisor_delete #'supervisor_delete
:supervisor_deactivate #'supervisor_deactivate
:create_token #'create_token
:invalidate_token #'invalidate_token}}))
:invalidate_token #'invalidate_token
:supervisor_password_reset #'supervisor_password_reset}}))

(defn ->graphql
"Create a wrapped graphql-executor, that merges context into the request.
Expand Down
37 changes: 37 additions & 0 deletions backend/src/swlkup/resolver/root/ngo/supervisor_password_reset.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
(ns swlkup.resolver.root.ngo.supervisor-password-reset
(:require [clojure.spec.alpha :as s]
[specialist-server.type :as t]
[swlkup.auth.core :refer [auth+role->entity]]
[swlkup.model.auth :as auth]
[swlkup.model.ngo :as ngo]
[swlkup.model.login :as login]
[swlkup.auth.password.generate :refer [generate-password]]
[swlkup.auth.password.hash :refer [hash-password]]))

(s/def ::supervisor t/string)

(s/fdef supervisor_password_reset
:args (s/tuple map? (s/keys :req-un [::auth/auth ::supervisor]) map? map?)
:ret (s/keys :req-un [::login/mail ::login/password]))

(defn supervisor_password_reset
[_node opt ctx _info]
(let [{:keys [q_id_unary tx_sync, tx-committed?]} (:db_ctx ctx)
[ngo:id] (auth+role->entity ctx (:auth opt) ::ngo/doc)
supervisor (:supervisor opt)]
(when ngo:id
(let [login (q_id_unary '{:find [(pull ?l [*])]
:where [[?l :xt/spec ::login/doc]
[?l :invited-by ?ngo:id]
[?l :mail ?mail]]
:in [[?ngo:id ?mail]]}
[ngo:id supervisor])
password (generate-password)
password-hash (hash-password password)
t (when login
(tx_sync [[:xtdb.api/put (assoc login
:password-hash password-hash)]]))]
(when (tx-committed? t)
{:mail (:mail login) :password password})))))

(s/def ::supervisor_password_reset (t/resolver #'supervisor_password_reset))
2 changes: 1 addition & 1 deletion backend/test/swlkup/introspection_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
(get-in [:data :__schema :types]))
(map :name)
sort)
'("Auth" "Boolean" "Contacts" "ContactsInput" "Float" "ID" "Int" "Location" "LocationInput" "Long" "MutationType" "NgoRefs" "QueryType" "String" "SupervisorInput" "created_tokens" "export" "languages" "login" "lookup" "ngo" "ngos" "offers" "supervisor_get" "supervisors" "supervisors_registered"))))
'("Auth" "Boolean" "Contacts" "ContactsInput" "Float" "ID" "Int" "Location" "LocationInput" "Long" "MutationType" "NgoRefs" "QueryType" "String" "SupervisorInput" "created_tokens" "export" "languages" "login" "lookup" "ngo" "ngos" "offers" "supervisor_get" "supervisor_password_reset" "supervisors" "supervisors_registered"))))
30 changes: 22 additions & 8 deletions frontend/codegen/generates.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/components/ngo/InvalidateTokenDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useAuthStore, AuthState } from '../Login'
import { fetcher } from '../../codegen/fetcher'
import { useNgoQuery, Created_Tokens } from '../../codegen/generates'
import { Created_Tokens } from '../../codegen/generates'
import { useTranslation } from 'react-i18next';
import create from 'zustand'

Expand Down
24 changes: 18 additions & 6 deletions frontend/components/ngo/InviteSupervisor.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useEffect } from 'react'
import { useAuthStore, AuthState, jwtFromLocalStorage } from '../Login'
import { SupervisorPasswordReset } from './SupervisorPasswordReset'
import { fetcher } from '../../codegen/fetcher'
import { useNgoQuery } from '../../codegen/generates'
import { useTranslation, Trans } from 'react-i18next';
import { useNgoQuery, Supervisors_Registered } from '../../codegen/generates'
import { useTranslation } from 'react-i18next';
import {Supervisor} from '../user/Supervisor'

async function mutate(auth: AuthState, mail: string) {
const result = await fetcher<any, any>(`mutation Invite($auth: Auth!, $mail: String) {
Expand All @@ -20,8 +22,8 @@ export function InviteSupervisor() {
}, [auth.jwt])

const { data, remove, refetch } = useNgoQuery({auth}, {enabled: Boolean(auth.jwt)})
const registered_active = data?.supervisors_registered?.filter(s => s.name_full)
const registered_new = data?.supervisors_registered?.filter(s => !s.name_full)
const registered_active = data?.supervisors_registered?.filter((s: Supervisors_Registered) => s.name_full)
const registered_new = data?.supervisors_registered?.filter((s: Supervisors_Registered) => !s.name_full)

return auth.jwt && (
<div style={{width: "100%"}}>
Expand All @@ -43,13 +45,23 @@ export function InviteSupervisor() {
{ Boolean(registered_active?.length) &&
<>
<h4>{registered_active?.length} { t('Active registered supervisors') }:</h4>
<p>{ registered_active?.map(s => s.name_full + ' (' + s.mail + ')').join(', ') }</p>
{ registered_active?.map((s: Supervisors_Registered) =>
<p>
{ s.name_full + ' (' + s.mail + ')' } &nbsp;
<SupervisorPasswordReset supervisor={s.mail} />
</p>
) }
</>
}
{ Boolean(registered_new?.length) &&
<>
<h5>{registered_new?.length} { t('New registered supervisors') }:</h5>
<p>{ registered_new?.map(s => s.mail).join(', ') }</p>
{ registered_new?.map((s: Supervisors_Registered) =>
<p>
{ s.mail } &nbsp;
<SupervisorPasswordReset supervisor={s.mail} />
</p>
) }
</>
}
</form>
Expand Down
45 changes: 45 additions & 0 deletions frontend/components/ngo/SupervisorPasswordReset.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useAuthStore, AuthState } from '../Login'
import { fetcher } from '../../codegen/fetcher'
import { useTranslation } from 'react-i18next';
import create from 'zustand'

type SupervisorMail = string;
type SupervisorPassword = string;

interface PasswortMap {
[Key: SupervisorMail]: SupervisorPassword;
}

export interface PasswortMapState {
passwortMap: PasswortMap,
addPasswort: (mail: SupervisorMail, password: SupervisorPassword) => void,
}

export const usePasswortMapStore = create<PasswortMapState>(set => ({
passwortMap: {},
addPasswort: (mail, password) => set( orig => ({passwortMap: {...orig.passwortMap, [mail]: password}})),
}))

async function pwReset (auth: AuthState, supervisor: string, addPasswort: PasswortMapState["addPasswort"]) {
const result = await fetcher<any, any>(`mutation SupervisorPasswordReset($auth: Auth!, $supervisor: SupervisorMail!) {
supervisor_password_reset(auth: $auth, supervisor: $supervisor){mail, password} }`,
{auth, supervisor})()
//console.log(result)
const password = result.supervisor_password_reset.password
addPasswort(supervisor, password)
}

export function SupervisorPasswordReset({supervisor}: {supervisor: string}) {
const {t} = useTranslation()
const auth = useAuthStore()
const {passwortMap, addPasswort} = usePasswortMapStore()

return (
<>
<input type="button" onClick={async () => { await pwReset(auth, supervisor, addPasswort) } }
value={ t('Reset Password') as string } /*name={supervisor}*/ />
&nbsp;
{ passwortMap[supervisor] }
</>
)
}
8 changes: 7 additions & 1 deletion frontend/pages/supervisor/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,20 @@ export default function SupervisorEdit() {
<td>{ t('Name') }</td>
<td><input type="text" name="name_full" defaultValue={supervisor?.name_full || undefined} required={true}/></td>
</tr>
<tr>
<td>{ t('job_title') }</td>
<td style={{width: "50%"}}>
<input type="text" name="text_job_title" defaultValue={"TODO"/*supervisor?.text_specialization || undefined*/} required={true}/>
</td>
</tr>
<tr>
<td>{ t('Specialization') }</td>
<td rowSpan={2} style={{width: "50%"}}>
<input type="text" name="text_specialization" defaultValue={supervisor?.text_specialization || undefined} required={true}/>
</td>
</tr>
<tr>
<td>
<td colSpan={2}>
<div className={styles_core.explanation}>
{ t('Specialization_examples') }
</div>
Expand Down

0 comments on commit 62ad93e

Please sign in to comment.