Skip to content

Commit

Permalink
Added a TOTP secret-key input field in the automatic login section (#145
Browse files Browse the repository at this point in the history
)

* added totp secret-key input field

* fix check_user_data to check for otp

* added some documentation on 2FA and TOTP
  • Loading branch information
f10d0 authored Feb 9, 2024
1 parent 5bea269 commit 2d8abcc
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 6 deletions.
39 changes: 39 additions & 0 deletions docs/2FA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Zwei-Faktor-Authentifizierung (2FA)
Die Idee der Zwei-Faktor-Authentifizierung ist deinen Login sicherer zu gestalten. Dabei kommt neben deinem normalen Passwort ein **Zweiter Faktor** zum Einsatz, den du bei der Anmeldung mit eingeben musst. \
Das allgemeine Konzept von 2FA ist:
- etwas, das du weißt: dein normales Passwort
- etwas, das du hast: bspw. dein Handy, welches dir bei jeder Anmeldung ein zusätzliches einmaliges Passwort generiert

Mehr Informationen zu 2FA-Tokens und wie du dein Token erstellst findest du auf der Seite vom ZIH [hier](https://faq.tickets.tu-dresden.de/otrs/public.pl?Action=PublicFAQZoom;ItemID=872).

TUfast kann bereits bei der Erstellung auf der ZIH Seite dein Token automatisch für dich abspeichern. Solltest du TUfast auf mehreren Rechnern verwenden wollen oder dein Token noch nach der Erstellung in TUfast abspeichern wollen, musst du dies manuell tun.

## TOTP (Time based one-time password)
Das TOTP Token solltest du bei der Erstellung über einen QR-Code von der ZIH Webseite mit einer Authenticator App auf deinem Handy gescannt haben.
Im nachfolgenden ist für eine Auswahl von Apps beschrieben, wie du das Token anzeigen kannst, um es dann in TUfast einzutragen.

### [Aegis](https://getaegis.app/) (empfohlen)
1. auf dem Hauptbildschirm der App hältst du auf dem Token gedrückt bis das Menü in der Kopfzeile der App aufgeht
![Aegis Main](assets/images/aegis_main_page.jpg)
2. drück auf den Stift und auf der nächsten Seite auf "Advanced" (ganz unten)
3. bei dem Feld "Secret" drückst du rechts auf das Augensymbol
![Aegis Edit](assets/images/aegis_edit_page.jpg)
4. dieses Secret musst du jetzt in das TOTP Feld in der "Automatisches Anmelden" Seite von TUfast eingeben
5. **das Token ist 32 Zeichen lang, du wirst nicht alle Zeichen direkt sehen und musst wahrscheinlich noch mit dem Cursor nach rechts gehen in dem Feld; sei dabei vorsichtig nichts in dem Feld zu bearbeiten ansonsten funktioniert dein ganzes Token nicht mehr**
6. geh dann oben links auf das Kreuz, sodass sich das Bearbeitungsfenster wieder schließt (Falls die App dich fragt ob du Änderungen verwerfen willst, hast du irgendwo ausversehen etwas geändert. Geh dann auf "Verwerfen" und mach das ganze nochmal um sicherzustellen, dass du dich bei dem Secret nicht verschrieben hast)

### [2FAS](https://2fas.com/)
1. auf dem Hauptbildschirm der App hältst du auf dem Token gedrückt bis von der Unterseite des Bildschirms ein Menü aufgeht
![2FAS Main](assets/images/2FAS_main_page.jpg)
2. geh dort auf "Bearbeiten" und im nächsten Fenster bei dem Feld "Secret Key" auf das Augensymbol
3. die App wird dich dazu auffordern eine PIN oder Fingerabdruck hinzuzufügen um dieses Feld anzeigen zu können, falls du dies noch nicht gemacht hast, folg dazu den Anweisungen der App und komm zu dieser Seite zurück
![2FAS Edit](assets/images/2FAS_edit_page.jpg)
4. diesen "Secret Key" musst du jetzt in das TOTP Feld in der "Automatisches Anmelden" Seite von TUfast eingeben
5. **das Token ist 32 Zeichen lang, du wirst nicht alle Zeichen direkt sehen und musst das Feld noch nach links schieben um den Rest zu sehen**
6. danach gehst du oben links wieder auf den Zurückpfeil

### privacyIDEA Authenticator (nicht empfohlen)
Die App bietet weder die Möglichkeit den geheimen Schlüssel deines Tokens anzuzeigen, noch deine Tokens zu exportieren. Erstelle dir im ZIH Portal ein neues Token (dabei wird dein altes Token ungültig) und benutze bitte eine der oben genannten Apps.

### Google Authenticator
Google Authenticator zeigt dir deinen geheimen Schlüssel des Tokens nicht direkt an. Theoretisch könntest du mit [diesem](https://github.com/scito/extract_otp_secrets) Projekt die Schlüssel dennoch exportieren. Am einfachsten ist es jedoch wenn du dir im ZIH Portal ein neues Token (dabei wird dein altes Token ungültig) erstellt und dann eine der oben genannten Apps (Aeris oder 2FAS) benutzt.
Binary file added docs/assets/images/2FAS_edit_page.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/2FAS_main_page.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/aegis_edit_page.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/aegis_main_page.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 12 additions & 1 deletion src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,13 @@ chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => {
return true // required for async sendResponse
case 'check_user_data':
// Asynchronous response
credentials.userDataExists(request.platform).then(sendResponse)
Promise.all([
credentials.userDataExists(request.platform),
credentials.userDataExists(request.platform + "-totp"),
credentials.userDataExists(request.platform + "-iotp")
]).then(([loginExists, totpExists, iotpExists]) => {
sendResponse(loginExists || totpExists || iotpExists)
});
return true // required for async sendResponse
case 'delete_user_data':
// Asynchronous response
Expand Down Expand Up @@ -254,6 +260,11 @@ chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => {

default: return sendResponse(false)
}
case 'delete_otp':
credentials.deleteUserData((request.platform ?? 'zih') + '-totp')
.then(() => credentials.deleteUserData((request.platform ?? 'zih') + '-iotp'))
.then(() => sendResponse(true))
return true
/* OWA */
case 'enable_owa_fetch':
owaFetch.enableOWAFetch().then(sendResponse)
Expand Down
10 changes: 7 additions & 3 deletions src/freshContent/settings/composables/logins.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Login } from '../types/Login'
import type { Login, Login2FA } from '../types/Login'

export const useLogins = () => ({
logins
Expand All @@ -17,8 +17,12 @@ const logins: Login[] = [
"Der Nutzername hat die Form 's3276763' oder 'luka075d'. Speichere deine aktuelle Eingabe nur, wenn du dir sicher bist.",
passwordPlaceholder: 'Passwort (selma-Login)',
passwordPattern: /.{5,}/,
passwordError: 'Das Passwort muss mindestens 5 Zeichen lang sein!'
},
passwordError: 'Das Passwort muss mindestens 5 Zeichen lang sein!',
totpSecretPlaceholder: 'TOTP Secret-Key in Base32',
totpSecretPattern: /^[A-Z2-7]{32}$/, // Base32 encoded
totpSecretError:
'Der TOTP Secret-Key besteht aus Großbuchstaben (A bis Z) und Ziffern (2 bis 7) und ist 32 Zeichen lang.'
} as Login2FA,
{
id: 'slub',
title: 'Werde automatisch auf der SLUB-Seite angemeldet.',
Expand Down
68 changes: 66 additions & 2 deletions src/freshContent/settings/settingPages/AutoLogin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,34 @@
:disabled="!(passwordValid && usernameValid)"
@click="submitSave"
/>
</div>

<div v-if="currentLogin2FA">
<p class="max-line p-margin">
Hier kannst du deinen TOTP Secret-Key speichern, sodass dein Second-Factor beim Login automatisch eingetragen wird.
Der Key ist Base32 enkodiert und sieht bspw. so aus: <br>
MHSTKUIKTTHPQAZNVWQBJE5YQ2WACQQP <br>
Für mehr Informationen zu TOTP und woher du deinen Secret-Key bekommst siehe <a href="https://github.com/TUfast-TUD/TUfast_TUD/blob/main/docs/2FA.md">hier</a>.
</p>
<div class="form">
<CustomInput
v-model="totpSecret"
v-model:valid="totpSecretValid"
:pattern="currentLogin2FA.totpSecretPattern"
:placeholder="currentLogin2FA.totpSecretPlaceholder"
:error-message="currentLogin2FA.totpSecretError"
warn
/>
<CustomButton
title="TOTP Key lokal speichern"
:disabled="!totpSecretValid"
@click="submitSaveTotp"
/>
</div>
</div>
<br>

<div class="form">
<CustomButton
title="Daten löschen"
class="button--warn"
Expand All @@ -52,7 +80,7 @@
</template>

<script lang="ts">
import { ref, defineComponent, watchEffect } from 'vue'
import { ref, defineComponent, watchEffect, computed } from 'vue'
// components
import Input from '../components/Input.vue'
Expand All @@ -64,6 +92,12 @@ import { useLogins } from '../composables/logins'
import { useChrome } from '../composables/chrome'
import { useUserData } from '../composables/user-data'
import type { Login, Login2FA } from '../types/Login'
function isLogin2FA (login: Login | Login2FA): login is Login2FA {
return 'totpSecretPattern' in login
}
export default defineComponent({
components: {
CustomInput: Input,
Expand All @@ -81,6 +115,8 @@ export default defineComponent({
const password = ref('')
const usernameValid = ref(false)
const passwordValid = ref(false)
const totpSecret = ref('')
const totpSecretValid = ref(false)
const autoLoginActive = ref(false)
Expand Down Expand Up @@ -111,9 +147,33 @@ export default defineComponent({
})) as boolean
}
const submitSaveTotp = async () => {
const secret = totpSecret.value
await sendChromeRuntimeMessage({
cmd: 'set_otp',
otpType: 'totp',
secret,
platform: currentLogin.value.id
})
totpSecret.value = ''
totpSecretValid.value = false
currentLogin.value.state = (await sendChromeRuntimeMessage({
cmd: 'check_user_data',
platform: currentLogin.value.id
})) as boolean
}
const currentLogin2FA = computed(() => {
return isLogin2FA(currentLogin.value) ? currentLogin.value as Login2FA : null
})
const submitDelete = async () => {
// await this one to get back the new value in last line, otherwise could run too late
await deleteUserData(currentLogin.value.id)
await sendChromeRuntimeMessage({
cmd: 'delete_otp',
platform: currentLogin.value.id
})
currentLogin.value.state = (await sendChromeRuntimeMessage({
cmd: 'check_user_data',
platform: currentLogin.value.id
Expand All @@ -128,8 +188,12 @@ export default defineComponent({
usernameValid,
passwordValid,
autoLoginActive,
currentLogin2FA,
totpSecret,
totpSecretValid,
submitSave,
submitDelete
submitDelete,
submitSaveTotp
}
}
})
Expand Down
6 changes: 6 additions & 0 deletions src/freshContent/settings/types/Login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@ export interface Login {
passwordPattern: RegExp,
passwordError: string,
}

export interface Login2FA extends Login{
totpSecretPlaceholder: string,
totpSecretPattern: RegExp
totpSecretError: string
}

0 comments on commit 2d8abcc

Please sign in to comment.