diff --git a/API_CHANGELOG.md b/API_CHANGELOG.md index ad12a79ad..00ff5efb5 100644 --- a/API_CHANGELOG.md +++ b/API_CHANGELOG.md @@ -4,9 +4,18 @@ In this we try to keep track of changes to the API. Primarily this should document changes that are not backwards compatible or belongs to already documented endpoints. This is to help you keep track of the changes and to help you update your code accordingly. +## 2023-08-09 + +Drop endpoint `POST /api/v1/auth/signup` ([#1772](https://github.com/Traewelling/traewelling/issues/1772)) + +> **warning** +> Endpoint `POST /api/v1/auth/login` will be removed in the future as well. +> Please migrate to OAuth2 as soon as possible. + ## 2023-08-06.2 -Renamed ~~`overriddenDeparture`~~ to `manualDeparture` and ~~`overriddenArrival`~~ to `manualArrival` in all endpoints which +Renamed ~~`overriddenDeparture`~~ to `manualDeparture` and ~~`overriddenArrival`~~ to `manualArrival` in all endpoints +which return a `Status` object ([#1809](https://github.com/Traewelling/traewelling/pull/1809)) Affected endpoints: @@ -25,7 +34,8 @@ Affected endpoints: > [!IMPORTANT] > **Backwards compatibility will be kept until 2023-10** -> The old attributes ~~`real_departure`~~ and ~~`real_arrival`~~ will still work until 2023-10, then they will be removed. +> The old attributes ~~`real_departure`~~ and ~~`real_arrival`~~ will still work until 2023-10, then they will be +> removed. > Affected endpoints will return both named attributes until then. ## 2023-08-06.1 @@ -35,4 +45,5 @@ endpoint `PUT /api/v1/status/{id}` ([#1809](https://github.com/Traewelling/traew > [!IMPORTANT] > **Backwards compatibility will be kept until 2023-10** -> The old attributes ~~`real_departure`~~ and ~~`real_arrival`~~ will still work until 2023-10, then they will be removed. +> The old attributes ~~`real_departure`~~ and ~~`real_arrival`~~ will still work until 2023-10, then they will be +> removed. diff --git a/app/Http/Controllers/API/v1/AuthController.php b/app/Http/Controllers/API/v1/AuthController.php index 1d537c976..0b68437e9 100644 --- a/app/Http/Controllers/API/v1/AuthController.php +++ b/app/Http/Controllers/API/v1/AuthController.php @@ -4,167 +4,30 @@ use App\Http\Controllers\Backend\Auth\LoginController; use App\Http\Resources\UserSettingsResource; -use App\Models\User; +use App\Providers\AuthServiceProvider; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Facades\Validator; -use App\Providers\AuthServiceProvider; -use Illuminate\Validation\ValidationException; class AuthController extends Controller { - /** - * @OA\Post( - * path="/auth/signup", - * operationId="registerUser", - * tags={"Auth"}, - * summary="register new user", - * @OA\RequestBody( - * required=true, - * @OA\JsonContent( - * @OA\Property( - * property="username", - * type="string", - * minLength=3, - * maxLength=25, - * pattern="^[a-zA-Z0-9_]*$", - * description="Username", - * example="Gertrud123" - * ), - * @OA\Property ( - * property="name", - * type="string", - * maxLength=50, - * ), - * @OA\Property ( - * property="email", - * example="mail@example.com" - * ), - * @OA\Property( - * property="password", - * description="password", - * type="string", - * minLength=8, - * maxLength=255, - * example="thisisnotasecurepassword123" - * ), - * @OA\Property ( - * property="password_confirmation", - * description="confirmation of the password-field.", - * type="string", - * minLength=8, - * maxLength=255, - * example="thisisnotasecurepassword123" - * ) - * ) - * ), - * @OA\Response( - * response=200, - * description="successful operation", - * @OA\JsonContent( - * @OA\Property(property="data", type="object", - * ref="#/components/schemas/BearerTokenResponse" - * ) - * ) - * ), - * @OA\Response(response=401, description="Other (not specified) error occured"), - * @OA\Response(response=422, description="Username or email is already taken, or other input error") - * ) - * - * - * @param Request $request - * - * @return JsonResponse - * @throws ValidationException - * @api v1 - */ - public function register(Request $request): JsonResponse { - $validator = Validator::make($request->all(), [ - 'username' => ['required', 'unique:users', 'min:3', 'max:25', 'regex:/^[a-zA-Z0-9_]*$/'], - 'name' => ['required', 'max:50'], - 'email' => ['required', 'email', 'unique:users', 'max:255'], - 'password' => ['required', 'confirmed', 'max:255'], - ]); - - if ($validator->fails()) { - return response()->json([ - 'status' => 'error', - 'errors' => $validator->errors() - ], 422); - } - - $validated = $validator->validated(); - $validated['password'] = Hash::make($validated['password']); - $validated['last_login'] = now(); - $user = User::create($validated); - - if ($user->wasRecentlyCreated) { - $userToken = $user->createToken('token', array_keys(AuthServiceProvider::$scopes)); - return $this->sendResponse( - data: [ - 'token' => $userToken->accessToken, - 'expires_at' => $userToken->token->expires_at->toIso8601String() - ], - code: 201 - ); - } - return $this->sendError("Sorry! Registration is not successful.", 401); - } /** - * @OA\Post( - * path="/auth/login", - * operationId="loginUser", - * tags={"Auth"}, - * summary="Login with username & password", - * @OA\RequestBody( - * required=true, - * @OA\JsonContent( - * @OA\Property( - * property="login", - * type="string", - * minLength=8, - * maxLength=255, - * description="Username or email", - * example="gertrud@traewelling.de" - * ), - * @OA\Property( - * property="password", - * description="password", - * type="string", - * minLength=8, - * maxLength=255, - * example="thisisnotasecurepassword123" - * ) - * ) - * ), - * @OA\Response( - * response=200, - * description="successful operation", - * @OA\JsonContent( - * @OA\Property(property="data", type="object", - * ref="#/components/schemas/BearerTokenResponse" - * ) - * ) - * ), - * @OA\Response(response=400, description="Bad request"), - * @OA\Response(response=401, description="Non-matching credentials") - * ) - * - * * @param Request $request * * @return JsonResponse - * @api v1 + * @deprecated Remove before 2023-10! Maybe earlier - if possible. Deprecation is already announced since + * November'22. */ public function login(Request $request): JsonResponse { $validated = $request->validate(['login' => ['required', 'max:255'], 'password' => ['required', 'min:8', 'max:255']]); if (LoginController::login($validated['login'], $validated['password'])) { $token = $request->user()->createToken('token', array_keys(AuthServiceProvider::$scopes)); - return $this->sendResponse(['token' => $token->accessToken, - 'expires_at' => $token->token->expires_at->toIso8601String()]) + return $this->sendResponse([ + 'WARNING' => 'This endpoint (login) is deprecated and will be removed in the following weeks. Please migrate to use OAuth2. More information: https://github.com/Traewelling/traewelling/issues/1772', + 'token' => $token->accessToken, + 'expires_at' => $token->token->expires_at->toIso8601String(), + ]) ->header('Authorization', $token->accessToken); } return $this->sendError('Non-matching credentials', 401); diff --git a/routes/api.php b/routes/api.php index 1c28f0ee7..21aac4054 100644 --- a/routes/api.php +++ b/routes/api.php @@ -33,8 +33,7 @@ Route::group(['prefix' => 'v1', 'middleware' => ['return-json']], static function() { Route::group(['prefix' => 'auth'], function() { Route::post('login', [v1Auth::class, 'login']); - Route::post('signup', [v1Auth::class, 'register']); - Route::group(['middleware' => 'auth:api'], function() { + Route::group(['middleware' => 'auth:api'], static function() { Route::post('refresh', [v1Auth::class, 'refresh']); Route::post('logout', [v1Auth::class, 'logout']); Route::get('user', [v1Auth::class, 'user']); diff --git a/storage/api-docs/api-docs.json b/storage/api-docs/api-docs.json index e123df37b..314284df9 100644 --- a/storage/api-docs/api-docs.json +++ b/storage/api-docs/api-docs.json @@ -23,137 +23,6 @@ } ], "paths": { - "/auth/signup": { - "post": { - "tags": [ - "Auth" - ], - "summary": "register new user", - "operationId": "registerUser", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "properties": { - "username": { - "description": "Username", - "type": "string", - "maxLength": 25, - "minLength": 3, - "pattern": "^[a-zA-Z0-9_]*$", - "example": "Gertrud123" - }, - "name": { - "type": "string", - "maxLength": 50 - }, - "email": { - "example": "mail@example.com" - }, - "password": { - "description": "password", - "type": "string", - "maxLength": 255, - "minLength": 8, - "example": "thisisnotasecurepassword123" - }, - "password_confirmation": { - "description": "confirmation of the password-field.", - "type": "string", - "maxLength": 255, - "minLength": 8, - "example": "thisisnotasecurepassword123" - } - }, - "type": "object" - } - } - } - }, - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "properties": { - "data": { - "$ref": "#/components/schemas/BearerTokenResponse" - } - }, - "type": "object" - } - } - } - }, - "401": { - "description": "Other (not specified) error occured" - }, - "422": { - "description": "Username or email is already taken, or other input error" - } - } - } - }, - "/auth/login": { - "post": { - "tags": [ - "Auth" - ], - "summary": "Login with username & password", - "operationId": "loginUser", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "properties": { - "login": { - "description": "Username or email", - "type": "string", - "maxLength": 255, - "minLength": 8, - "example": "gertrud@traewelling.de" - }, - "password": { - "description": "password", - "type": "string", - "maxLength": 255, - "minLength": 8, - "example": "thisisnotasecurepassword123" - } - }, - "type": "object" - } - } - } - }, - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "properties": { - "data": { - "$ref": "#/components/schemas/BearerTokenResponse" - } - }, - "type": "object" - } - } - } - }, - "400": { - "description": "Bad request" - }, - "401": { - "description": "Non-matching credentials" - } - } - } - }, "/auth/logout": { "post": { "tags": [ @@ -2007,9 +1876,6 @@ "401": { "description": "Unauthorized" }, - "404": { - "description": "No statuses found" - }, "403": { "description": "User not authorized to access this" } @@ -5757,9 +5623,9 @@ "scheme": "https", "flows": { "authorizationCode": { - "authorizationUrl": "http://localhost/oauth/authorize", - "tokenUrl": "http://localhost/oauth/token", - "refreshUrl": "http://localhost/auth/refresh", + "authorizationUrl": "http://localhost:8000/oauth/authorize", + "tokenUrl": "http://localhost:8000/oauth/token", + "refreshUrl": "http://localhost:8000/auth/refresh", "scopes": { "read-statuses": "see all statuses", "read-notifications": "see your notifications", diff --git a/tests/ApiTestCase.php b/tests/ApiTestCase.php index c938dd3a0..d87d6b073 100644 --- a/tests/ApiTestCase.php +++ b/tests/ApiTestCase.php @@ -2,12 +2,13 @@ namespace Tests; +use App\Models\User; +use App\Providers\AuthServiceProvider; use Illuminate\Testing\TestResponse; abstract class ApiTestCase extends TestCase { - public $mockConsoleOutput = false; - private ?string $token = null; + public $mockConsoleOutput = false; public function setUp(): void { parent::setUp(); @@ -16,30 +17,7 @@ public function setUp(): void { } protected function getTokenForTestUser(): string { - if ($this->token === null) { - $username = 'john_doe' . time() . rand(111, 999); - $response = $this->postJson('/api/v1/auth/signup', [ - 'username' => $username, - 'name' => 'John Doe', - 'email' => $username . '@example.com', - 'password' => 'thisisnotasecurepassword123', - 'password_confirmation' => 'thisisnotasecurepassword123', - ]); - $response->assertCreated(); - $response->assertJsonStructure([ - 'data' => [ - 'token', - 'expires_at', - ] - ]); - $this->token = $response->json('data.token'); - - $response = $this->put('/api/v1/settings/acceptPrivacy', [], [ - 'Authorization' => 'Bearer ' . $this->token, - ]); - $response->assertNoContent(); - } - return $this->token; + return User::factory()->create()->createToken('token', array_keys(AuthServiceProvider::$scopes)); } protected function assertUserResource(TestResponse $response): void { diff --git a/tests/Feature/APIv1/AuthTest.php b/tests/Feature/APIv1/AuthTest.php index 6b69341e6..1d1e6175d 100644 --- a/tests/Feature/APIv1/AuthTest.php +++ b/tests/Feature/APIv1/AuthTest.php @@ -12,81 +12,6 @@ class AuthTest extends ApiTestCase use RefreshDatabase; - public function testRegisterLoginAndLogout(): void { - // 1. Register with failing validator - $response = $this->postJson('/api/v1/auth/signup', [ - //Missing username -> validator should fail - 'name' => 'John Doe', - 'email' => 'john@example.com', - 'password' => 'thisisnotasecurepassword123', - 'password_confirmation' => 'thisisnotasecurepassword123', - ]); - $response->assertUnprocessable(); - - // 2. Successful register - $response = $this->postJson('/api/v1/auth/signup', [ - 'username' => 'john_doe', - 'name' => 'John Doe', - 'email' => 'john@example.com', - 'password' => 'thisisnotasecurepassword123', - 'password_confirmation' => 'thisisnotasecurepassword123', - ]); - $response->assertCreated(); - $response->assertJsonStructure([ - 'data' => [ - 'token', - 'expires_at', - ] - ]); - - // 3. Login with failing validator - $response = $this->postJson('/api/v1/auth/login', [ - 'login' => 'something random', - 'password' => 'not the correct password', - ]); - $response->assertUnauthorized(); - - // 4. Successful login - $response = $this->postJson('/api/v1/auth/login', [ - 'login' => 'john_doe', - 'password' => 'thisisnotasecurepassword123', - ]); - $response->assertOk(); - $response->assertJsonStructure([ - 'data' => [ - 'token', - 'expires_at', - ] - ]); - $token = $response->json()['data']['token']; - - // 5. See current user - $response = $this->get('/api/v1/auth/user', [ - 'Authorization' => 'Bearer ' . $token, - ]); - $response->assertOk(); - $this->assertUserResource($response); - $this->assertEquals('john_doe', $response->json('data.username')); - - // 6. Refresh token - $response = $this->post('/api/v1/auth/refresh', [], [ - 'Authorization' => 'Bearer ' . $token, - ]); - $response->assertOk(); - $response->assertJsonStructure([ - 'data' => [ - 'token', - 'expires_at', - ] - ]); - - // 7. Logout / Revoke token - $response = $this->post('/api/v1/auth/logout', [], [ - 'Authorization' => 'Bearer ' . $token, - ]); - $response->assertOk(); - } - public function testAccessWithRevokedTokenIsNotPossible(): void { $user = User::factory()->create(); $token = $user->createToken('token', array_keys(AuthServiceProvider::$scopes));