Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a TOTP secret-key input field in the automatic login section #145

Merged
merged 3 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
C0ntroller marked this conversation as resolved.
Show resolved Hide resolved

## 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
}
Loading