Commit 4a2e4617 authored by Dominik Hebeler's avatar Dominik Hebeler
Browse files

Merge branch '1122-implement-method-for-changing-the-member-key' into 'development'

Resolve "Implement Method for Changing the member key"

Closes #1122

See merge request !1856
parents c02ab818 d4842cd6
...@@ -6,37 +6,31 @@ use Cookie; ...@@ -6,37 +6,31 @@ use Cookie;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use LaravelLocalization; use LaravelLocalization;
use \App\Models\Key; use \App\Models\Key;
use \Carbon\Carbon;
use Validator;
class KeyController extends Controller class KeyController extends Controller
{ {
public function index(Request $request) // How many Ad Free searches should a user get max when he creates a new key
{ const KEYCHANGE_ADFREE_SEARCHES = 150;
$redirUrl = $request->input('redirUrl', "");
$cookie = Cookie::get('key');
$key = $request->input('keyToSet', '');
if (empty($key) && empty($cookie)) {
$key = 'enter_key_here';
} elseif (empty($key) && !empty($cookie)) {
$key = $cookie;
} elseif (!empty($key)) {
$key = $request->input('key');
}
public function index(\App\Models\Key $key, Request $request)
{
$cookieLink = LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('loadSettings', Cookie::get())); $cookieLink = LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('loadSettings', Cookie::get()));
return view('key') return view('key')
->with('title', trans('titles.key')) ->with('title', trans('titles.key'))
->with('cookie', $key) ->with('keystatus', $key->getStatus())
->with('cookie', $key->key)
->with('cookieLink', $cookieLink); ->with('cookieLink', $cookieLink);
} }
public function setKey(Request $request) public function setKey(Request $request)
{ {
$redirUrl = $request->input('redirUrl', "");
$keyToSet = $request->input('keyToSet'); $keyToSet = $request->input('keyToSet');
$key = new Key($request->input('keyToSet', '')); $key = new Key($request->input('keyToSet', ''));
if ($key->getStatus()) { $status = $key->getStatus();
if ($status !== null) {
# Valid Key # Valid Key
$host = $request->header("X_Forwarded_Host", ""); $host = $request->header("X_Forwarded_Host", "");
if (empty($host)) { if (empty($host)) {
...@@ -46,10 +40,7 @@ class KeyController extends Controller ...@@ -46,10 +40,7 @@ class KeyController extends Controller
$settings = Cookie::get(); $settings = Cookie::get();
$settings['key'] = $keyToSet; $settings['key'] = $keyToSet;
$cookieLink = LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('loadSettings', $settings)); $cookieLink = LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('loadSettings', $settings));
return view('key') return redirect(LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('keyindex')));
->with('title', trans('titles.key'))
->with('cookie', $keyToSet)
->with('cookieLink', $cookieLink);
} else { } else {
$cookieLink = LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('loadSettings', Cookie::get())); $cookieLink = LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('loadSettings', Cookie::get()));
return view('key') return view('key')
...@@ -74,4 +65,152 @@ class KeyController extends Controller ...@@ -74,4 +65,152 @@ class KeyController extends Controller
return redirect($url); return redirect($url);
} }
} }
public function changeKeyIndex(\App\Models\Key $key, Request $request){
if(!$key->canChange()){
return redirect(LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('keyindex')));
}
return view('keychange', [
"title" => trans('titles.keychange'),
"key" => $key->key,
"css" => [mix('css/keychange/index.css')]
]);
}
public function removeCurrent(\App\Models\Key $key, Request $request){
if(!$key->canChange()){
return redirect(LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('keyindex')));
}
// Reduce Current Key
$res = $key->reduce(self::KEYCHANGE_ADFREE_SEARCHES);
if(empty($res) || empty($res->status) || $res->status !== "success"){
return redirect(LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('keyindex')));
}
// Redirect to Cookie Remove URL with redirect to step two
$validUntil = Carbon::now("Europe/London")->addDays(2);
$format = "Y-m-d H:i:s";
$data = [
"validUntil" => $validUntil->format($format),
"password" => hash_hmac("sha256", $validUntil->format($format), env("APP_KEY", "WEAK_KEY")),
];
$targetUrl = LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('changeKeyTwo', $data));
$redirUrl = LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('removeCookie', [
"ir" => $targetUrl
]));
return redirect($redirUrl);
}
public function generateNew(\App\Models\Key $key, Request $request){
// Validate Request Data
$validUntil = $request->input('validUntil', '');
$password = $request->input('password', '');
$format = "Y-m-d H:i:s";
// Check if Validuntil
$valid = true;
if(empty($validUntil)){
$valid = false;
}else{
$validUntil = Carbon::createFromFormat($format, $validUntil, "Europe/London");
if(!$validUntil){
$valid = false;
}
}
if($valid && Carbon::now()->diffInSeconds($validUntil) <= 0){
$valid = false;
}
if($valid){
// Check if hash matches
$expectedHash = hash_hmac("sha256", $validUntil->format($format), env("APP_KEY", "WEAK_KEY"));
if(!hash_equals($expectedHash, $password)){
$valid = false;
}
}
if(!$valid){
return redirect(LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('keyindex')));
}
// Check if the key already was generated
if (!$key->checkForChange("", $password)) {
return redirect(LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('keyindex')));
}
return view('keychangetwo', [
"title" => trans('titles.keychange'),
"validUntil" => $validUntil,
"css" => [mix('css/keychange/index.css')]
]);
}
public function generateNewPost(\App\Models\Key $key, Request $request){
// Validate Request Data
$validUntil = $request->input('validUntil', '');
$password = $request->input('password', '');
$format = "Y-m-d H:i:s";
// Check if Validuntil
$valid = true;
if(empty($validUntil)){
$valid = false;
}else{
$validUntil = Carbon::createFromFormat($format, $validUntil, "Europe/London");
if(!$validUntil){
$valid = false;
}
}
if($valid && Carbon::now()->diffInSeconds($validUntil) <= 0){
$valid = false;
}
if($valid){
// Check if hash matches
$expectedHash = hash_hmac("sha256", $validUntil->format($format), env("APP_KEY", "WEAK_KEY"));
if(!hash_equals($expectedHash, $password)){
$valid = false;
}
}
if(!$valid){
return redirect(LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('keyindex')));
}
$validator = Validator::make($request->all(), [
'newkey' => 'required|min:4|max:20',
]);
if($validator->fails()) {
$data = [
"validUntil" => $validUntil->format($format),
"password" => hash_hmac("sha256", $validUntil->format($format), env("APP_KEY", "WEAK_KEY")),
"newkey" => $request->input('newkey', ''),
];
$targetUrl = LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('changeKeyTwo', $data));
return redirect($targetUrl);
}
$newkey = $request->input('newkey', '');
$characters = '0123456789abcdefghijklmnopqrstuvwxyz';
$randomSuffix = "";
$suffixCount = 3;
for($i = 0; $i < $suffixCount; $i++){
$randomSuffix .= $characters[rand(0, strlen($characters)-1)];
}
$newkey = $newkey . $randomSuffix;
if($key->checkForChange($newkey, $password)){
$result = $key->generateKey(null, self::KEYCHANGE_ADFREE_SEARCHES, $newkey, "Schlüssel gewechselt. Hash $password");
if(!empty($result)){
Cookie::queue('key', $result, 525600, '/', null, false, false);
return redirect(LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('changeKeyThree', ["newkey" => $result])));
}
}
$data = [
"validUntil" => $validUntil->format($format),
"password" => hash_hmac("sha256", $validUntil->format($format), env("APP_KEY", "WEAK_KEY")),
];
$targetUrl = LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('changeKeyTwo', $data));
return redirect($targetUrl);
}
} }
...@@ -220,7 +220,7 @@ class MailController extends Controller ...@@ -220,7 +220,7 @@ class MailController extends Controller
$betrag = round($betrag, 2, PHP_ROUND_HALF_DOWN); $betrag = round($betrag, 2, PHP_ROUND_HALF_DOWN);
# Generating personalised key for donor # Generating personalised key for donor
$key = app('App\Models\Key')->generateKey($betrag); $key = app('App\Models\Key')->generateKey($betrag, null, null, 'Für ' . $betrag . '€ aufgeladen am '. date("d.m.Y"));
try { try {
$postdata = [ $postdata = [
......
...@@ -17,7 +17,7 @@ class RemoveKey ...@@ -17,7 +17,7 @@ class RemoveKey
public function handle($request, Closure $next) public function handle($request, Closure $next)
{ {
// Check if a wrong Key Cookie is set and if so remove it // Check if a wrong Key Cookie is set and if so remove it
if(Cookie::has("key") && !app('App\Models\Key')->getStatus()){ if(Cookie::has("key") && app('App\Models\Key')->getStatus() === null){
return redirect(route("removeCookie", ["ir" => url()->full()])); return redirect(route("removeCookie", ["ir" => url()->full()]));
} }
return $next($request); return $next($request);
......
...@@ -1502,7 +1502,6 @@ class MetaGer ...@@ -1502,7 +1502,6 @@ class MetaGer
$logEntry .= " time=" . round((microtime(true) - $this->starttime), 2) . " serv=" . $this->fokus; $logEntry .= " time=" . round((microtime(true) - $this->starttime), 2) . " serv=" . $this->fokus;
$logEntry .= " interface=" . LaravelLocalization::getCurrentLocale(); $logEntry .= " interface=" . LaravelLocalization::getCurrentLocale();
$logEntry .= " sprachfilter=" . $this->lang; $logEntry .= " sprachfilter=" . $this->lang;
$logEntry .= " key=" . $this->apiKey;
$logEntry .= " eingabe=" . $this->eingabe; $logEntry .= " eingabe=" . $this->eingabe;
$logEntry = preg_replace("/\n+/", " ", $logEntry); $logEntry = preg_replace("/\n+/", " ", $logEntry);
......
...@@ -4,12 +4,14 @@ namespace App\Models; ...@@ -4,12 +4,14 @@ namespace App\Models;
use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Redis;
use Request; use Request;
use \Carbon\Carbon;
class Key class Key
{ {
public $key; public $key;
public $status; # valid key = true, invalid key = false, unidentified key = null public $status; # Null If Key invalid | false if valid but has no adFreeSearches | true if valid and has adFreeSearches
private $keyserver = "https://key.metager.de/"; private $keyserver = "https://key.metager.de/";
private $keyinfo;
public function __construct($key, $status = null) public function __construct($key, $status = null)
{ {
...@@ -25,35 +27,29 @@ class Key ...@@ -25,35 +27,29 @@ class Key
{ {
if ($this->key !== '' && $this->status === null) { if ($this->key !== '' && $this->status === null) {
$this->updateStatus(); $this->updateStatus();
if(empty($this->status)){ if($this->status === null){
// The user provided an invalid key which we will log to fail2ban // The user provided an invalid key which we will log to fail2ban
$fail2banEnabled = config("metager.metager.fail2ban_enabled"); $fail2banEnabled = config("metager.metager.fail2ban_enabled");
if(empty($fail2banEnabled) || !$fail2banEnabled || !env("fail2banurl", false) || !env("fail2banuser") || !env("fail2banpassword")){ if (!empty($fail2banEnabled) && $fail2banEnabled && !empty(env("fail2banurl", false)) && !empty(env("fail2banuser")) && !empty(env("fail2banpassword"))) {
return false; // Submit fetch job to worker
$mission = [
"resulthash" => "captcha",
"url" => env("fail2banurl") . "/mgkeytry/",
"useragent" => "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0",
"username" => env("fail2banuser"),
"password" => env("fail2banpassword"),
"headers" => [
"ip" => Request::ip()
],
"cacheDuration" => 0,
"name" => "Captcha",
];
$mission = json_encode($mission);
Redis::rpush(\App\MetaGer::FETCHQUEUE_KEY, $mission);
} }
// Submit fetch job to worker
$mission = [
"resulthash" => "captcha",
"url" => env("fail2banurl") . "/mgkeytry/",
"useragent" => "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0",
"username" => env("fail2banuser"),
"password" => env("fail2banpassword"),
"headers" => [
"ip" => Request::ip()
],
"cacheDuration" => 0,
"name" => "Captcha",
];
$mission = json_encode($mission);
Redis::rpush(\App\MetaGer::FETCHQUEUE_KEY, $mission);
} }
} }
if ($this->status === null || $this->status === false) { return $this->status;
return false;
} else {
return true;
}
} }
public function updateStatus() public function updateStatus()
...@@ -71,14 +67,15 @@ class Key ...@@ -71,14 +67,15 @@ class Key
try { try {
$link = $this->keyserver . "v2/key/". urlencode($this->key); $link = $this->keyserver . "v2/key/". urlencode($this->key);
$result = json_decode(file_get_contents($link, false, $context)); $result = json_decode(file_get_contents($link, false, $context));
if ($result->{'apiAccess'} == 'unlimited') { if(!empty($result)){
$this->status = true; $this->keyinfo = $result;
return true; if($this->keyinfo->adFreeSearches > 0 || $this->keyinfo->apiAccess === "unlimited"){
} else if ($result->{'apiAccess'} == 'normal' && $result->{'adFreeSearches'} > 0){ $this->status = true;
$this->status = true; }else{
$this->status = false;
}
return true; return true;
} else { }else{
$this->status = false;
return false; return false;
} }
} catch (\ErrorException $e) { } catch (\ErrorException $e) {
...@@ -118,15 +115,26 @@ class Key ...@@ -118,15 +115,26 @@ class Key
return false; return false;
} }
} }
public function generateKey($payment) public function generateKey($payment = null, $adFreeSearches = null, $key = null, $notes = "")
{ {
$authKey = base64_encode(env("KEY_USER", "test") . ':' . env("KEY_PASSWORD", "test")); $authKey = base64_encode(env("KEY_USER", "test") . ':' . env("KEY_PASSWORD", "test"));
$postdata = http_build_query(array( $postdata = array(
'payment' => $payment,
'apiAccess' => 'normal', 'apiAccess' => 'normal',
'notes' => 'Fuer ' . $payment . '€ aufgeladen am '. date("d.m.Y"), 'expiresAfterDays' => 365,
'expiresAfterDays' => 365 'notes' => $notes
)); );
if(!empty($key)){
$postdata["key"] = $key;
}
if(!empty($payment)){
$postdata["payment"] = $payment;
}else if(!empty($adFreeSearches)){
$postdata["adFreeSearches"] = $adFreeSearches;
}else{
return false;
}
$postdata = http_build_query($postdata, "", "&", PHP_QUERY_RFC3986);
$opts = array( $opts = array(
'http' => array( 'http' => array(
'method' => 'POST', 'method' => 'POST',
...@@ -149,4 +157,89 @@ class Key ...@@ -149,4 +157,89 @@ class Key
return false; return false;
} }
} }
public function reduce($count){
$authKey = base64_encode(env("KEY_USER", "test") . ':' . env("KEY_PASSWORD", "test"));
$postdata = http_build_query(array(
'adFreeSearches' => $count,
));
$opts = array(
'http' => array(
'method' => 'POST',
'header' => [
'Content-type: application/x-www-form-urlencoded',
'Authorization: Basic ' . $authKey
],
'content' => $postdata,
'timeout' => 5
),
);
$context = stream_context_create($opts);
try {
$link = $this->keyserver . "v2/key/" . $this->key . "/reduce-searches";
$result = json_decode(file_get_contents($link, false, $context));
return $result;
} catch (\ErrorException $e) {
return false;
}
}
/**
* Tells if this key is liable to change to a custom key
* Currently only members are allowed to do so and only every 2 days
* Also only the original member key is allowed to be changed
*
* @return boolean
*/
public function canChange(){
if(empty($this->status) || !preg_match("/^Mitgliederschlüssel\./", $this->keyinfo->notes) || $this->keyinfo->adFreeSearches < \App\Http\Controllers\KeyController::KEYCHANGE_ADFREE_SEARCHES){
return false;
}
if(!empty($this->keyinfo->KeyChangedAt)){
// "2021-03-09T09:19:44.000Z"
$keyChangedAt = Carbon::createFromTimeString($this->keyinfo->KeyChangedAt, 'Europe/London');
if($keyChangedAt->diffInSeconds(Carbon::now()) > (2 * 24 * 60 * 60)){
return true;
}else{
return false;
}
}
return true;
}
public function checkForChange($newkey = "", $hash){
$authKey = base64_encode(env("KEY_USER", "test") . ':' . env("KEY_PASSWORD", "test"));
$postdata = http_build_query(array(
'hash' => $hash,
'key' => $newkey,
));
$opts = array(
'http' => array(
'method' => 'POST',
'header' => [
'Content-type: application/x-www-form-urlencoded',
'Authorization: Basic ' . $authKey
],
'content' => $postdata,
'timeout' => 5
),
);
$context = stream_context_create($opts);
try {
$link = $this->keyserver . "v2/key/can-change";
$result = json_decode(file_get_contents($link, false, $context));
if(!empty($result) && $result->status === "success" && empty($result->results)){
return true;
}else{
return false;
}
} catch (\ErrorException $e) {
return false;
}
}
} }
...@@ -4,7 +4,7 @@ return [ ...@@ -4,7 +4,7 @@ return [
'h1' => "Schlüssel für Ihre werbefreie Suche", 'h1' => "Schlüssel für Ihre werbefreie Suche",
'p1' => 'MetaGer bietet <a href=":url1">SUMA-EV Mitgliedern</a> und großzügigen <a href=":url2">Spendern</a> einen Schlüssel an, mit dem sie Zugriff auf ein Kontingent an werbefreien Suchen haben.', 'p1' => 'MetaGer bietet <a href=":url1">SUMA-EV Mitgliedern</a> und großzügigen <a href=":url2">Spendern</a> einen Schlüssel an, mit dem sie Zugriff auf ein Kontingent an werbefreien Suchen haben.',
'p2' => 'Auf dieser Seite können Sie Ihren Schlüssel (sofern bekannt) eingeben. Wir speichern diesen mit Hilfe eines Cookies auf Ihrem PC. Auf diese Weise sendet Ihr Browser den Schlüssel automatisch bei jeder durchgeführten Suche an uns, sodass wir die Werbung für Sie entfernen können.', 'p2' => 'Auf dieser Seite können Sie Ihren Schlüssel (sofern bekannt) eingeben. Wir speichern diesen mit Hilfe eines Cookies auf Ihrem PC. Auf diese Weise sendet Ihr Browser den Schlüssel automatisch bei jeder durchgeführten Suche an uns, sodass wir die Werbung für Sie entfernen können.',
'p3' => 'Wenn Sie sich den Cookie anschauen steht dort drin "key=xxxx". Wir verwenden diesen dementsprechend nicht für Tracking-Zwecke. Er wird auch zu keinem Zeitpunkt in irgendeiner Form von uns gespeichert oder geloggt.', 'p3' => 'Wenn Sie sich den Cookie anschauen steht dort drin "key=xxxx". Er wird zu keinem Zeitpunkt in irgendeiner Form von uns gespeichert oder geloggt. Wir verwenden diesen insbesondere auch nicht für Tracking-Zwecke',
'p4' => 'Wichtig: Um diese Funktion nutzen zu können, müssen Sie Cookies in Ihrem Browser zugelassen haben. Die Einstellung bleibt dann solange gespeichert, wie Ihr Browser Cookies speichert.', 'p4' => 'Wichtig: Um diese Funktion nutzen zu können, müssen Sie Cookies in Ihrem Browser zugelassen haben. Die Einstellung bleibt dann solange gespeichert, wie Ihr Browser Cookies speichert.',
'p5' => 'Um den Schlüssel darüber hinausgehend speichern zu können haben Sie folgende Möglichkeiten:', 'p5' => 'Um den Schlüssel darüber hinausgehend speichern zu können haben Sie folgende Möglichkeiten:',
'li1' => 'Richten Sie sich folgenden Link als Startseite/Lesezeichen ein:', 'li1' => 'Richten Sie sich folgenden Link als Startseite/Lesezeichen ein:',
...@@ -12,5 +12,9 @@ return [ ...@@ -12,5 +12,9 @@ return [
'placeholder1' => 'Schlüssel eingeben...', 'placeholder1' => 'Schlüssel eingeben...',
'removeKey' => 'aktuellen Schlüssel entfernen', 'removeKey' => 'aktuellen Schlüssel entfernen',
'invalidKey' => 'Der eingegebene Schlüssel ist ungültig', 'invalidKey' => 'Der eingegebene Schlüssel ist ungültig',
'empty' => 'Ihr Schlüssel ist zwar gültig, enthält aber keine werbefreien Suchen mehr.',
'backLink' => 'Zurück zur letzten Seite', 'backLink' => 'Zurück zur letzten Seite',
'custom.h3' => 'Wunsch-Schlüssel',
'custom.p1' => 'Mitglieder des SUMA-EV haben die Möglichkeit, sich einen eigenen Schlüssel auszusuchen.',
'custom.a1' => 'Wunsch Schlüssel einrichten'
]; ];
<?php
return [
'h1' => 'Wunsch Schlüssel',
'p1' => 'Mit diesem Tool haben Sie die Möglichkeit Ihren aktuellen Mitgliederschlüssel ":key" zu wechseln. Mitgliederschlüssel sind an Ihre Mitgliedschaft im SUMA-EV gekoppelt. So kann dieser automatisch erneuert werden. Auch wenn wir das nicht tun, hätten wir dadurch natürlich theoretisch die Möglichkeit eine Verknüpfung zwischen Mitgliederschlüssel und durchgeführten Suchen zu schaffen. Mit dem Wechsel auf einen Wunsch Schlüssel wird eine solche Verknüpfung theoretisch und praktisch für uns unmöglich.',
'p2' => 'Außerdem können Sie mit diesem Tool einen Schlüssel erstellen, der leichter zu merken ist. Bitte heben Sie sich den ursprünglichen Schlüssel dennoch auf. Dadurch, dass der neue Schlüssel nicht mehr automatisch erneuert werden kann, wird dieser irgendwann ungültig. In dem Fall müssen Sie diesen Prozess wiederholen, indem Sie Ihren ursprünglichen Mitgliederschlüssel (:key) bei MetaGer eingeben und dieses Tool erneut aufrufen.',
'p3' => 'Heben Sie sich deshalb beide Schlüssel gut auf und beachten Sie insbesondere, dass wir nicht in der Lage sind Ihnen den neuen, durch diesen Wechsel entstandenen, Schlüssel bei Verlust mitzuteilen oder wiederherzustellen.',
'p4' => 'Dieses Tool führt Sie durch folgende Schritte um einen neuen anonymen Wunsch Schlüssel zu erstellen:',
'ol1.li1' => 'Entfernen aller Informationen über den aktuellen Mitgliederschlüssel aus Ihrem Browser',
'ol1.li2' => 'Erzeugen einer URL mit der ein neuer Schlüssel erstellt werden kann. Diese URL enthält keine Informationen mehr über den Mitgliederschlüssel.',
'ol1.li3' => 'Erzeugen eines neuen Wunsch Schlüssels',
'ol1.li4' => 'Speichern des neuen Wunsch Schlüssels im Browser',
'a1' => 'Gelesen - Wunsch Schlüssel erstellen',
'p5' => 'Ihr bisheriger Mitgliederschlüssel wurde nun aus dem Browser gelöscht. Außerdem haben wir eine URL erzeugt, mit der Sie sich Ihren Wunschschlüssel erstellen können. Dies ist innerhalb von :validUntil jederzeit und von jedem Gerät aus möglich indem Sie folgende URL im Browser aufrufen, oder direkt das Formular auf dieser Seite verwenden.',
'p6' => 'In das nachfolgende Textfeld können, Sie nun Ihren gewünschten Schlüssel eintragen. Um Überschneidungen zu vermeiden fügen wir noch ein paar Zeichen an den Schlüssel an. Mit dem Klick auf generieren wird Ihr neuer Schlüssel dann erzeugt und auf der folgenden Seite angezeigt, damit Sie Ihn sich abspeichern können.',
'input1label' => 'Wunsch Schlüssel eintragen (Mindestens 4 und maximal 20 Zeichen)',
'input1' => 'Wunsch Schlüssel eintragen',
'button1' => 'Wunsch Schlüssel generieren',
'p7' => ' Ihr neuer Wunsch Schlüssel wurde erfolgreich eingerichtet. Bitte merken Sie ihn sich gut. Er kann bei Verlust nämlich nicht wiederhergestellt werden. Er wird außerdem irgendwann ungültig werden. Wenn der Fall eintritt, tragen Sie bei MetaGer bitte Ihren regulären Mitgliederschlüssel ein und wiederholen Sie diesen Vorgang.',
'p8' => 'Ihr neuer Schlüssel lautet:',
];
\ No newline at end of file
...@@ -24,6 +24,7 @@ return [ ...@@ -24,6 +24,7 @@ return [
'asso' => 'Assoziator - MetaGer', 'asso' => 'Assoziator - MetaGer',
'plugin' => 'Plugin - MetaGer',