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

Update API docs & deprecate some older ones #1360

Merged
merged 6 commits into from
Oct 4, 2022
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
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@
- API is now versioned separately from selfoss and follows [semantic versioning](https://semver.org/) ([#1137](https://github.com/fossar/selfoss/pull/1137))
- *API 2.21.0*: `/mark` now accepts list of item IDs encoded as JSON. Requests using `application/x-www-form-urlencoded` are deprecated. ([#1182](https://github.com/fossar/selfoss/pull/1182))
- Dates returned as part of items now strictly follow ISO8601 format. ([#1246](https://github.com/fossar/selfoss/pull/1246))
- The following are deprecated and will be removed in next selfoss version:
- Passing credentials in query string, use cookies instead. ([#1360](https://github.com/fossar/selfoss/pull/1360))
- `GET /login` endpoint, use `POST /login`. ([#1360](https://github.com/fossar/selfoss/pull/1360))
- `GET /logout` was deprecated in favour of newly introduced (*API 4.1.0*) `DELETE /api/session/current`. ([#1360](https://github.com/fossar/selfoss/pull/1360))
- `POST /source/delete/:id` in favour of `DELETE /source/:id`. ([#1360](https://github.com/fossar/selfoss/pull/1360))

### Customization changes
- `selfoss.shares.register` was removed. Instead you should set `selfoss.customSharers` to an object of *sharer* objects. The `action` callback is now expected to open a window on its own, instead of returning a URL. A label and a HTML code of an icon (you can use a `<img>` tag, inline `<svg>`, emoji, etc.) are now expected.
Expand Down
7 changes: 7 additions & 0 deletions assets/js/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@ export class HttpError extends Error {
this.name = 'HttpError';
}
}

export class LoginError extends Error {
constructor(message) {
super(message);
this.name = 'LoginError';
}
}
3 changes: 3 additions & 0 deletions assets/js/helpers/ajax.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ export const get = liftToPromiseField(option('method', 'GET'))(fetch);
export const post = liftToPromiseField(option('method', 'POST'))(fetch);


export const delete_ = liftToPromiseField(option('method', 'DELETE'))(fetch);


/**
* Using URLSearchParams directly handles dictionaries inconveniently.
* For example, it joins arrays with commas or includes undefined keys.
Expand Down
4 changes: 3 additions & 1 deletion assets/js/helpers/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export function i18nFormat(translated, params) {
case ',':
if (placeholder) {
if (state == 'index') {
placeholder.index = parseInt(buffer.trim());
const index = buffer.trim();
const intIndex = parseInt(index);
placeholder.index = Number.isNaN(intIndex) ? index : intIndex;
placeholder.value = params[placeholder.index];
buffer = '';
} else if (state == 'type') {
Expand Down
13 changes: 11 additions & 2 deletions assets/js/requests/common.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LoginError } from '../errors';
import * as ajax from '../helpers/ajax';

/**
Expand All @@ -16,12 +17,20 @@ export function getInstanceInfo() {
export function login(credentials) {
return ajax.post('login', {
body: new URLSearchParams(credentials)
}).promise.then(response => response.json());
}).promise.then(
response => response.json()
).then((data) => {
if (data.success) {
return Promise.resolve();
} else {
return Promise.reject(new LoginError(data.error));
}
});
}

/**
* Terminates the active user session.
*/
export function logout() {
return ajax.get('logout').promise;
return ajax.delete_('api/session/current').promise;
}
2 changes: 1 addition & 1 deletion assets/js/requests/sources.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function refreshAll() {
* Removes source with given ID.
*/
export function remove(id) {
return ajax.post(`source/delete/${id}`).promise;
return ajax.delete_(`source/${id}`).promise;
}

/**
Expand Down
18 changes: 6 additions & 12 deletions assets/js/selfoss-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,19 +199,13 @@ var selfoss = {
username,
password
};
return login(credentials).then((data) => {
if (data.success) {
selfoss.setSession();
selfoss.history.push('/');
// init offline if supported and not inited yet
return login(credentials).then(() => {
selfoss.setSession();
// init offline if supported and not inited yet
selfoss.dbOffline.init();
if ((!selfoss.db.storage || selfoss.db.broken) && selfoss.db.enableOffline.value) {
// Initialize database in offline mode when it has not been initialized yet or it got broken.
selfoss.dbOffline.init();
if ((!selfoss.db.storage || selfoss.db.broken) && selfoss.db.enableOffline.value) {
// Initialize database in offline mode when it has not been initialized yet or it got broken.
selfoss.dbOffline.init();
}
return Promise.resolve();
} else {
return Promise.reject(new Error(data.error));
}
});
},
Expand Down
14 changes: 12 additions & 2 deletions assets/js/templates/LoginForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import { SpinnerBig } from './Spinner';
import { useHistory, useLocation } from 'react-router-dom';
import { HttpError, LoginError } from '../errors';
import { ConfigurationContext } from '../helpers/configuration';
import { LocalizationContext } from '../helpers/i18n';

Expand All @@ -20,9 +21,18 @@ function handleLogIn({

selfoss.login({ username, password, offlineEnabled }).then(() => {
history.push('/');
}).catch(() => {
}).catch((err) => {
const message =
err instanceof LoginError
? selfoss.app._('login_invalid_credentials')
: selfoss.app._('login_error_generic', {
errorMessage:
err instanceof HttpError
? `HTTP ${err.response.status} ${err.message}`
: err.message,
});
history.replace('/sign/in', {
error: selfoss.app._('login_invalid_credentials')
error: message,
});
}).finally(() => {
setLoading(false);
Expand Down
1 change: 1 addition & 0 deletions assets/locale/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"lang_login_username": "Jméno",
"lang_login_password": "Heslo",
"lang_login_invalid_credentials": "Špatné jméno nebo heslo",
"lang_login_error_generic": "Pokus o přihlášení selhal: {errorMessage}",
"lang_no_title": "Bez titulku",
"lang_error": "Nastala chyba",
"lang_error_unauthorized": "Pro pokračování se prosím {link_begin}přihlaste{link_end}.",
Expand Down
1 change: 1 addition & 0 deletions assets/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"lang_login_username": "Username",
"lang_login_password": "Password",
"lang_login_invalid_credentials": "Wrong username/password",
"lang_login_error_generic": "Could not log in: {errorMessage}",
"lang_login_offline": "Offline storage",
"lang_no_title": "No title",
"lang_experimental": "Experimental",
Expand Down
29 changes: 26 additions & 3 deletions docs/api-description.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"servers": [],
"info": {
"description": "You can access selfoss by using the same backend as selfoss user interface: The RESTful HTTP JSON API. There are a few urls where you can get information from selfoss and some for updating data. Assume you want all tags for rendering this in your own app. You have to make an HTTP GET call on the url /tags:\n\n```\nGET http://yourselfossurl.com/tags\n```\nThe result is following JSON formatted response (in this example two tags “blog” and “deviantart” are available:\n\n```\n[{\"tag\":\"blog\",\"color\":\"#251f10\",\"unread\":\"1\"},\n{\"tag\":\"deviantart\",\"color\":\"#e78e5c\",\"unread\":\"0\"}]\n```\n\nFollowing docs shows you which calls are possible and which response you can expect.",
"version": "4.0.0",
"version": "5.0.0",
"title": "selfoss"
},
"tags": [
{
"name": "Authentication",
"description": "When you are using a login protected page, you have to add the parameter `username` and `password` in every request (as GET or POST parameter). The logical consequence is that you have to use https.\n\nFo0r an initial login functionality in your app you can validate a username password combination using `GET /login`.\n\nAlternately, you can use session cookie on subsequent requests."
"description": "To access endpoints that require authentication, you need to pass a session [cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) with the request. To obtain it, sign in using `POST /login` API call – the HTTP response will contain a `Set-Cookie` header for `PHPSESSID` cookie, whose value you should store and pass it to the server on all subsequent API requests, using the `Cookie` header. Those requests can also generate a new session id and pass it to you again using `Set-Cookie` header in the response, in which case you should replace the stored value. The HTTP library you use will probably have a cookie jar support to handle this for you."
}
],
"paths": {
Expand Down Expand Up @@ -99,7 +99,30 @@
}
},
"/logout": {
"post": {
"get": {
"deprecated": true,
"tags": [
"Authentication"
],
"summary": "Deauthenticate the user",
"description": "Destroys the session on the server",
"operationId": "logoutLegacy",
"responses": {
"200": {
"description": "always true",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Response"
}
}
}
}
}
}
},
"/api/session/current": {
"delete": {
"tags": [
"Authentication"
],
Expand Down
8 changes: 6 additions & 2 deletions index.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@
$dice->create(controllers\Authentication::class)->password();
});
$router->get('/login', function() use ($dice) {
// json
// json, deprecated
$dice->create(controllers\Authentication::class)->login();
});
$router->post('/login', function() use ($dice) {
// json
$dice->create(controllers\Authentication::class)->login();
});
$router->get('/logout', function() use ($dice) {
// json, deprecated
$dice->create(controllers\Authentication::class)->logout();
});
$router->delete('/api/session/current', function() use ($dice) {
// json
$dice->create(controllers\Authentication::class)->logout();
});
Expand Down Expand Up @@ -121,7 +125,7 @@
$dice->create(controllers\Sources::class)->remove($id);
});
$router->post('/source/delete/([0-9]+)', function($id) use ($dice) {
// json
// json, deprecated
$dice->create(controllers\Sources::class)->remove($id);
});
$router->post('/source/([0-9]+)/update', function($id) use ($dice) {
Expand Down
2 changes: 1 addition & 1 deletion src/constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
// independent of selfoss version
// needs to be bumped each time public API is changed (follows semver)
// keep in sync with docs/api-description.json
const SELFOSS_API_VERSION = '4.0.0';
const SELFOSS_API_VERSION = '5.0.0';
jtojnar marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions src/controllers/Sources/Update.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public function __construct(Authentication $authentication, ContentLoader $conte
public function updateAll() {
// only allow access for localhost and loggedin users
if (!$this->authentication->allowedToUpdate()) {
header('HTTP/1.0 403 Forbidden');
exit('unallowed access');
}

Expand All @@ -49,6 +50,7 @@ public function updateAll() {
public function update($id) {
// only allow access for localhost and authenticated users
if (!$this->authentication->allowedToUpdate()) {
header('HTTP/1.0 403 Forbidden');
exit('unallowed access');
}

Expand Down