Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.75% covered (success)
97.75%
87 / 89
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
CreateUserEndpoint
97.75% covered (success)
97.75%
87 / 89
0.00% covered (danger)
0.00%
0 / 1
26
0.00% covered (danger)
0.00%
0 / 1
 handle
97.75% covered (success)
97.75%
87 / 89
0.00% covered (danger)
0.00%
0 / 1
26
1<?php
2
3namespace Olz\Users\Endpoints;
4
5use Olz\Api\OlzCreateEntityTypedEndpoint;
6use Olz\Entity\AuthRequest;
7use Olz\Entity\Users\User;
8use PhpTypeScriptApi\Fields\ValidationError;
9use PhpTypeScriptApi\HttpError;
10
11/**
12 * @phpstan-import-type OlzUserId from UserEndpointTrait
13 * @phpstan-import-type OlzUserData from UserEndpointTrait
14 *
15 * @extends OlzCreateEntityTypedEndpoint<OlzUserId, OlzUserData, array{
16 *   captchaToken?: ?non-empty-string,
17 * }, array{
18 *   status: 'OK'|'OK_NO_EMAIL_VERIFICATION'|'DENIED'|'ERROR',
19 * }>
20 */
21class CreateUserEndpoint extends OlzCreateEntityTypedEndpoint {
22    use UserEndpointTrait;
23
24    protected function handle(mixed $input): mixed {
25        $current_user = $this->authUtils()->getCurrentUser();
26        $token = $input['custom']['captchaToken'] ?? null;
27        if (!$current_user && !$this->captchaUtils()->validateToken($token)) {
28            return ['custom' => ['status' => 'DENIED'], 'id' => null];
29        }
30
31        $parent_user_id = $input['data']['parentUserId'] ?? null;
32        if ($parent_user_id !== null) {
33            if (!$current_user) {
34                throw new HttpError(403, "Kein Zugriff!");
35            }
36            if ($parent_user_id !== $current_user->getId()) {
37                // Create child of someone else
38                if (!$this->authUtils()->hasPermission('users')) {
39                    throw new HttpError(403, "Kein Zugriff!");
40                }
41            }
42        }
43
44        $first_name = $input['data']['firstName'];
45        $last_name = $input['data']['lastName'];
46        $username = $input['data']['username'];
47        $email = $input['data']['email'] ?? null;
48        $password = $input['data']['password'] ?? null;
49        $this->log()->info("New sign-up (using password): {$first_name} {$last_name} ({$username}@) <{$email}> (Parent: {$parent_user_id})");
50        if (!$parent_user_id && !$email) {
51            throw new ValidationError(['email' => ["Feld darf nicht leer sein."]]);
52        }
53        if (!$parent_user_id && !$password) {
54            throw new ValidationError(['password' => ["Feld darf nicht leer sein."]]);
55        }
56        if (!$this->authUtils()->isUsernameAllowed($username)) {
57            throw new ValidationError(['username' => ["Der Benutzername darf nur Buchstaben, Zahlen, und die Zeichen -_. enthalten."]]);
58        }
59        if (!$parent_user_id && !$this->authUtils()->isPasswordAllowed($password)) {
60            throw new ValidationError(['password' => ["Das Passwort muss mindestens 8 Zeichen lang sein."]]);
61        }
62        if ($email && preg_match('/@olzimmerberg\.ch$/i', $email)) {
63            throw new ValidationError(['email' => ["Bitte keine @olzimmerberg.ch E-Mail verwenden."]]);
64        }
65        $ip_address = $this->server()['REMOTE_ADDR'];
66        $auth_request_repo = $this->entityManager()->getRepository(AuthRequest::class);
67        $user_repo = $this->entityManager()->getRepository(User::class);
68
69        $same_username_user = $user_repo->findOneBy(['username' => $username]);
70        $same_email_user = $user_repo->findOneBy(['email' => $email]);
71        // TODO: Test users with old username, roles
72        if ($username && $same_username_user) {
73            if ($same_username_user->getPasswordHash()) {
74                throw new ValidationError(['username' => ["Es existiert bereits eine Person mit diesem Benutzernamen. Wolltest du gar kein Konto erstellen, sondern dich nur einloggen?"]]);
75            }
76            // If it's an existing user WITHOUT password, we just update that existing user!
77            $entity = $same_username_user;
78            $this->entityUtils()->updateOlzEntity($entity, ['onOff' => true]);
79        } elseif ($email && $same_email_user) {
80            if ($same_email_user->getPasswordHash()) {
81                throw new ValidationError(['email' => ["Es existiert bereits eine Person mit dieser E-Mail Adresse. Wolltest du gar kein Konto erstellen, sondern dich nur einloggen?"]]);
82            }
83            // If it's an existing user WITHOUT password, we just update that existing user!
84            $entity = $same_email_user;
85            $this->entityUtils()->updateOlzEntity($entity, ['onOff' => true]);
86        } else {
87            $entity = new User();
88            $this->entityUtils()->createOlzEntity($entity, ['onOff' => true]);
89        }
90
91        $entity->setOldUsername(null);
92        $this->updateEntityWithData($entity, $input['data']);
93        $entity->setEmailIsVerified(false);
94        $entity->setEmailVerificationToken(null);
95
96        $password_hash = $password ? $this->authUtils()->hashPassword($password) : null;
97
98        $entity->setPasswordHash($password_hash);
99        $entity->setParentUserId($parent_user_id);
100        $entity->setPermissions('');
101        $entity->setRoot(null);
102        $entity->setMemberType(null);
103        $entity->setMemberLastPaid(null);
104        $entity->setWantsPostalMail(false);
105        $entity->setPostalTitle(null);
106        $entity->setPostalName(null);
107        $entity->setJoinedOn(null);
108        $entity->setJoinedReason(null);
109        $entity->setLeftOn(null);
110        $entity->setLeftReason(null);
111        $entity->setNotes('');
112        $entity->setLastLoginAt(null);
113
114        $this->entityManager()->persist($entity);
115        $this->entityManager()->flush();
116        $this->persistUploads($entity, $input['data']);
117
118        if (!$parent_user_id) {
119            $this->session()->resetConfigure(['timeout' => 3600]);
120
121            $root = $entity->getRoot() !== '' ? $entity->getRoot() : './';
122            $this->session()->set('auth', $entity->getPermissions());
123            $this->session()->set('root', $root);
124            $this->session()->set('user', $entity->getUsername());
125            $this->session()->set('user_id', "{$entity->getId()}");
126            $this->session()->set('auth_user', $entity->getUsername());
127            $this->session()->set('auth_user_id', "{$entity->getId()}");
128            $auth_request_repo->addAuthRequest($ip_address, 'AUTHENTICATED_PASSWORD', $entity->getUsername());
129
130            $this->emailUtils()->setLogger($this->log());
131            try {
132                $this->emailUtils()->sendEmailVerificationEmail($entity);
133            } catch (\Throwable $th) {
134                return [
135                    'custom' => ['status' => 'OK_NO_EMAIL_VERIFICATION'],
136                    'id' => $entity->getId() ?? 0,
137                ];
138            }
139            $this->entityManager()->flush();
140        }
141
142        return [
143            'custom' => ['status' => 'OK'],
144            'id' => $entity->getId() ?? 0,
145        ];
146    }
147}