wip(backend): concluded apikeys routes and docs
This commit is contained in:
parent
d563bb2d17
commit
bd7c3e8b6e
9 changed files with 415 additions and 45 deletions
|
@ -12,38 +12,39 @@ refer to [the section on authenticating to the
|
|||
API](/reference/backend/api#authentication).
|
||||
</Tip>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Create a new API key
|
||||
|
||||
Create a new API key. The API key will belong to the user who is authenticated
|
||||
when making the call. Supported for both JWT and KEY authentication.
|
||||
|
||||
<Note compact>
|
||||
The response to this API call is the only time the secret will be
|
||||
revealed.
|
||||
</Note>
|
||||
|
||||
### Endpoints
|
||||
|
||||
| Method | Path | Description |
|
||||
| ------ | ---- | ----------- |
|
||||
| `POST` | `/apikey/jwt` | Create a new API key. Endpoint for JWT authentication |
|
||||
| `POST` | `/apikey/key` | Create a new API key. Endpoint for API key authentication |
|
||||
| Method | Path | Description | Auth |
|
||||
| ------ | ---- | ----------- | ---- |
|
||||
| <Method post /> | `/apikey/jwt` | Create a new API key | _jwt_ |
|
||||
| <Method post /> | `/apikey/key` | Create a new API key | _key_ |
|
||||
|
||||
### Parameters
|
||||
<Tabs tabs="Request, Response">
|
||||
<Tab>
|
||||
| Variable | Type | Description |
|
||||
| -------- | -------- | ----------- |
|
||||
| `name` | `string` | Create a new API key. Endpoint for JWT authentication |
|
||||
| `level` | `number` | A privilege level from 0 to 8. |
|
||||
| `expiresIn` | `number` | The number of seconds until this key expires. |
|
||||
| Where | Variable | Type | Description |
|
||||
| ----- | ------------- | -------- | ----------- |
|
||||
| _body_ | `name` | `string` | Create a new API key. Endpoint for JWT authentication |
|
||||
| _body_ | `level` | `number` | A privilege level from 0 to 8. |
|
||||
| _body_ | `expiresIn` | `number` | body | The number of seconds until this key expires. |
|
||||
</Tab>
|
||||
<Tab>
|
||||
Returns status code `200` on success, `400` on if the request is malformed, and
|
||||
`500` on server error.
|
||||
Returns HTTP status code <StatusCode status="201"/> on success, <StatusCode status="400"/> if
|
||||
the request is malformed, and <StatusCode status="500"/> on server error.
|
||||
|
||||
| Value | Type | Description |
|
||||
| ------------------- | -------- | ----------- |
|
||||
| `result` | `string` | `success` on success, and `error` on error |
|
||||
| `result` | `string` | `created` on success, and `error` on error |
|
||||
| `apikey.key` | `string` | The API key |
|
||||
| `apikey.secret` | `string` | The API secret |
|
||||
| `apikey.level` | `number` | The privilege level of the API key |
|
||||
|
@ -57,7 +58,7 @@ Returns status code `200` on success, `400` on if the request is malformed, and
|
|||
<Tabs tabs="Request, Response">
|
||||
<Tab>
|
||||
```js
|
||||
const token = axios.post(
|
||||
const apiKey = axios.post(
|
||||
'https://backend.freesewing.org/apikey/jwt',
|
||||
{
|
||||
name: 'My first API key',
|
||||
|
@ -90,6 +91,185 @@ const token = axios.post(
|
|||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Read an API key
|
||||
|
||||
Reads an existing API key. Note that the API secret can only be retrieved at
|
||||
the moment the API key is created.
|
||||
|
||||
<Note compact>
|
||||
You need the `admin` role to read API keys of other users
|
||||
</Note>
|
||||
### Endpoints
|
||||
|
||||
| Method | Path | Description | Auth |
|
||||
| ------ | ---- | ----------- | ---- |
|
||||
| <Method get /> | `/apikey/:id/jwt` | Reads an API key | _jwt_ |
|
||||
| <Method get /> | `/apikey/:id/key` | Reads an API key | _key_ |
|
||||
|
||||
### Parameters
|
||||
<Tabs tabs="Request, Response">
|
||||
<Tab>
|
||||
| Where | Variable | Type | Description |
|
||||
| ----- | ----------- | -------- | ----------- |
|
||||
| _url_ | `:id` | `string` | The `key` field of the API key |
|
||||
</Tab>
|
||||
<Tab>
|
||||
Returns HTTP status code <StatusCode status="200"/> on success, <StatusCode status="400"/> if
|
||||
the request is malformed, <StatusCode status="404"/> if the key is not found,
|
||||
and <StatusCode status="500"/> on server error.
|
||||
|
||||
| Value | Type | Description |
|
||||
| ------------------- | -------- | ----------- |
|
||||
| `result` | `string` | `success` on success, and `error` on error |
|
||||
| `apikey.key` | `string` | The API key |
|
||||
| `apikey.level` | `number` | The privilege level of the API key |
|
||||
| `apikey.expiresAt` | `string` | A string representation of the moment the API key expires |
|
||||
| `apikey.name` | `string` | The name of the API key |
|
||||
| `apikey.userId` | `number` | The ID of the user who created the API key |
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
### Example
|
||||
<Tabs tabs="Request, Response">
|
||||
<Tab>
|
||||
```js
|
||||
const keyInfo = axios.get(
|
||||
'https://backend.freesewing.org/apikey/7ea12968-7758-40b6-8c73-75cc99be762b/jwt',
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab>
|
||||
|
||||
```json
|
||||
{
|
||||
result: 'success',
|
||||
apikey: {
|
||||
key: '7ea12968-7758-40b6-8c73-75cc99be762b',
|
||||
level: 3,
|
||||
expiresAt: '2022-11-06T15:57:30.190Z',
|
||||
name: 'My first API key',
|
||||
userId: 61
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Read the current API key
|
||||
|
||||
Reads the API key with which the current request was authenticated.
|
||||
|
||||
### Endpoints
|
||||
|
||||
| Method | Path | Description | Auth |
|
||||
| ------ | ---- | ----------- | ---- |
|
||||
| <Method get /> | `/whoami/key` | Reads the current API key | _key_ |
|
||||
|
||||
### Parameters
|
||||
<Tabs tabs="Request, Response">
|
||||
<Tab>
|
||||
<Note compact>This endpoint takes no parameters</Note>
|
||||
</Tab>
|
||||
<Tab>
|
||||
Returns status code `200` on success, `400` on if the request is malformed,
|
||||
`404` if the key is not found, and `500` on server error.
|
||||
|
||||
| Value | Type | Description |
|
||||
| ------------------- | -------- | ----------- |
|
||||
| `result` | `string` | `success` on success, and `error` on error |
|
||||
| `apikey.key` | `string` | The API key |
|
||||
| `apikey.level` | `number` | The privilege level of the API key |
|
||||
| `apikey.expiresAt` | `string` | A string representation of the moment the API key expires |
|
||||
| `apikey.name` | `string` | The name of the API key |
|
||||
| `apikey.userId` | `number` | The ID of the user who created the API key |
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
### Example
|
||||
<Tabs tabs="Request, Response">
|
||||
<Tab>
|
||||
```js
|
||||
const keyInfo = axios.get(
|
||||
'https://backend.freesewing.org/whoami/key',
|
||||
{
|
||||
auth: {
|
||||
username: apikey.key,
|
||||
password: apikey.secret,
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab>
|
||||
|
||||
```json
|
||||
{
|
||||
result: 'success',
|
||||
apikey: {
|
||||
key: '7ea12968-7758-40b6-8c73-75cc99be762b',
|
||||
level: 3,
|
||||
expiresAt: '2022-11-06T15:57:30.190Z',
|
||||
name: 'My first API key',
|
||||
userId: 61
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Remove an API key
|
||||
|
||||
Removes an existing API key.
|
||||
|
||||
<Note compact>
|
||||
You need the `admin` role to remove API keys of other users
|
||||
</Note>
|
||||
|
||||
### Endpoints
|
||||
|
||||
| Method | Path | Description | Auth |
|
||||
| ------ | ---- | ----------- | ---- |
|
||||
| <Method delete /> | `/apikey/:id/jwt` | Removes an API key | _jwt_ |
|
||||
| <Method delete /> | `/apikey/:id/key` | Removes an API key | _key_ |
|
||||
|
||||
### Parameters
|
||||
<Tabs tabs="Request, Response">
|
||||
<Tab>
|
||||
| Where | Variable | Type | Description |
|
||||
| ----- | ----------- | -------- | ----------- |
|
||||
| _url_ | `:id` | `string` | The `key` field of the API key |
|
||||
</Tab>
|
||||
<Tab>
|
||||
Returns HTTP status code <StatusCode status="204"/> on success, <StatusCode status="400"/> if
|
||||
the request is malformed, <StatusCode status="404"/> if the key is not found,
|
||||
and <StatusCode status="500"/> on server error.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
### Example
|
||||
<Tabs tabs="Request, Response">
|
||||
<Tab>
|
||||
```js
|
||||
const keyInfo = axios.get(
|
||||
'https://backend.freesewing.org/apikey/7ea12968-7758-40b6-8c73-75cc99be762b/jwt',
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab>
|
||||
<Note compact>Status code <StatusCode status="204"/> (no content) does not come with a body</Note>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Notes
|
||||
|
||||
The following is good to keep in mind when working with API keys:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: REST API
|
||||
title: Backend REST API
|
||||
linktitle: REST API
|
||||
---
|
||||
|
||||
This is the reference documentation for the FreeSewing backend REST API.
|
||||
|
@ -19,30 +20,35 @@ automation tasks such as creating issues on Github.
|
|||
|
||||
## Authentication
|
||||
|
||||
This API is not accessible without authentication.
|
||||
Apart from a handlful of API endpoints that are accessible without
|
||||
authentication (typically the ones dealing with the signup flow or password
|
||||
recovery), this API requires authentication.
|
||||
|
||||
The FreeSewing backend API allows two types of authentication:
|
||||
Two different types of authentication are supports:
|
||||
|
||||
- JSON Web Tokens (jwt): This is typically used to authenticate humans in a
|
||||
- **JSON Web Tokens** (jwt): This is typically used to authenticate humans in a
|
||||
browser session.
|
||||
- API Keys (key): This is typically used to interact with the API in an
|
||||
automated way. Like in a script, or a CI/CD context.
|
||||
- **API Keys** (key): This is typically used to interact with the API in an
|
||||
automated way. Like in a script, a CI/CD context, a serverless runner, and so
|
||||
on.
|
||||
|
||||
Apart from the handlful of API endpoints that are accessible without
|
||||
authentication (typically the ones dealing with the signup flow), this API has
|
||||
a variant for each route depending on what authentication you want to use:
|
||||
For each endpoint, the API has a variant depending on what authentication you
|
||||
want to use:
|
||||
|
||||
- `/some/route/jwt` : Authenticate with JWT
|
||||
- `/some/route/key` : Authenticate with an API key and secret
|
||||
|
||||
### JWT authentication
|
||||
|
||||
The use of JSON Web Tokens ([jwt](https://jwt.io)) is typically used in a browser context where we want to establish a *session*.
|
||||
The use of JSON Web Tokens ([jwt](https://jwt.io)) is typically used in a
|
||||
browser context where we want to establish a *session*.
|
||||
|
||||
To get a token, you must first authenticate at the `/login` endpoint with username and password.
|
||||
You will receive a token in the response.
|
||||
To get a token, you must first authenticate at the `/login` endpoint with
|
||||
username and password. You will receive a JSON Web Token (jwt) as part of the
|
||||
response.
|
||||
|
||||
In subsequent API calls, you must then include this token in the `Authorization` header prefixed by `Bearer`. Liek his:
|
||||
In subsequent API calls, you must then include this token in the
|
||||
`Authorization` header prefixed by `Bearer`. Like his:
|
||||
|
||||
```js
|
||||
const account = await axios.get(
|
||||
|
@ -57,11 +63,18 @@ const account = await axios.get(
|
|||
|
||||
### API key authentication
|
||||
|
||||
The combination API key & secret serves as a username & password for HTTP basic authentication.
|
||||
In basic authentication, the password is sent unencrypted, but since the FreeSewing backend is only reachable over a TLS encrypted connection, this is not a problem.
|
||||
The combination of API key & secret serves as a username & password for [HTTP
|
||||
basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).
|
||||
|
||||
On the plus side, sending a username and password with a request is supported pretty much everywhere.
|
||||
In addition, there is no need to establish a session first, so this make the entire transation stateless.
|
||||
<Note>
|
||||
In basic authentication, the password is sent
|
||||
unencrypted. To guard against this, this API should only be served over a
|
||||
connectin encrypted with TLS. (a url starting with `https://`).
|
||||
</Note>
|
||||
|
||||
Sending a username and password with a request like this is supported
|
||||
pretty much everywhere. In addition, there is no need to establish a session
|
||||
first, so this make the entire transation stateless.
|
||||
|
||||
Below is an example using curl:
|
||||
|
||||
|
@ -96,8 +109,8 @@ The table below lists the priviledge of all levels as well as their correspondin
|
|||
| `4` | **write all** account data | ✅ | ✅ | ✅ | ✅ |
|
||||
| `5` | **read** measurements or patterns of **other users** | ❌ | ✅ | ✅ | ✅ |
|
||||
| `6` | **read all** account data of **other users** | ❌ | ❌ | ✅ | ✅ |
|
||||
| `7` | **write** access through **specific support methods** | ❌ | ❌ | ✅ | ✅ |
|
||||
| `8` | impersonate other user, full write access | ❌ | ❌ | ❌ | ✅ |
|
||||
| `7` | **write** account data of **other users** through **specific support methods** | ❌ | ❌ | ✅ | ✅ |
|
||||
| `8` | impersonate other users, **full write access** | ❌ | ❌ | ❌ | ✅ |
|
||||
|
||||
## API Routes
|
||||
|
||||
|
|
|
@ -2,4 +2,57 @@
|
|||
title: FreeSewing backend
|
||||
---
|
||||
|
||||
FIXME: Explain what this is about
|
||||
The FreeSewing backend handles all user data. Prior to version 3 of FreeSewing,
|
||||
the backend was only used internally as the data store for our frontend, the
|
||||
FreeSewing.org website.
|
||||
|
||||
In version 3, we have rewritten the backend with the explicit goal to offer it
|
||||
as a service to users and developers. This allows integration with other tools
|
||||
such as hosted instances of our lab, CLI tools, serverless runners, CI/CD
|
||||
environments and so on.
|
||||
|
||||
In other words, we no longer merely provide our own frontend, you can now also
|
||||
use our backend as a service to build your own projects.
|
||||
|
||||
## Changes for developers
|
||||
|
||||
### Authentication with JWT and API keys
|
||||
|
||||
Before version 3, the backend only supported authentication via JSON Web
|
||||
Tokens. That's fine for a browser session, but not very handy if you want to
|
||||
talk to the API directly.
|
||||
|
||||
Since version 3, we support authentication with API keys. Furthermore, we
|
||||
allow any FreeSewing user to generate their own API keys.
|
||||
|
||||
In other words, if you want to connect to our backend API, you don't need to
|
||||
ask us. You can generate your own API key and start right away.
|
||||
|
||||
We've made a number of changes to make it easier for external developers and
|
||||
contributors to work with our backend.
|
||||
|
||||
### Sqlite instead of MongoDB
|
||||
|
||||
Our backend used to use MongoDB for storage. Since version 3, we've moved to
|
||||
Sqlite which is a file-based database making local development a breeze since
|
||||
you don't need to run a local database server.
|
||||
|
||||
### Sanity instead of local storage
|
||||
|
||||
We now use Sanity and the Sanity API to stored images for users (avatars for
|
||||
user accounts and people). Furthermore, we also generate PDFs in the browser
|
||||
now so we also don't need storage for that.
|
||||
|
||||
As a result, our backend does not need any storage, only access to the Sqlite
|
||||
file. This also makes it easier to work with the backend as a developer.
|
||||
|
||||
## Use, don't abuse
|
||||
|
||||
Our backend API runs in a cloud environment and while we do not charge for
|
||||
access to the API, we do need to pay the bills of said cloud provider.
|
||||
|
||||
As such, please be mindful of the amount of requests you generate. And if you
|
||||
have big plans, please reach out to us to discuss them first.
|
||||
|
||||
We will monitor the use of our backend API and we may at any moment decide to
|
||||
revoke API keys if we feel the use is beyond what we can or want to support.
|
||||
|
|
|
@ -62,3 +62,16 @@ ApikeyController.prototype.whoami = async (req, res, tools) => {
|
|||
|
||||
return Apikey.sendResponse(res)
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove API key
|
||||
*
|
||||
* This is the endpoint that handles removal of API keys/tokens
|
||||
* See: https://freesewing.dev/reference/backend/api/apikey
|
||||
*/
|
||||
ApikeyController.prototype.delete = async (req, res, tools) => {
|
||||
const Apikey = new ApikeyModel(tools)
|
||||
await Apikey.removeIfAllowed({ id: req.params.id }, req.user)
|
||||
|
||||
return Apikey.sendResponse(res)
|
||||
}
|
||||
|
|
|
@ -25,7 +25,8 @@ ApikeyModel.prototype.setResponse = function (status = 200, error = false, data
|
|||
...data,
|
||||
},
|
||||
}
|
||||
if (status > 201) {
|
||||
if (status === 201) this.response.body.result = 'created'
|
||||
else if (status > 201) {
|
||||
this.response.body.error = error
|
||||
this.response.body.result = 'error'
|
||||
this.error = true
|
||||
|
@ -68,12 +69,34 @@ ApikeyModel.prototype.readIfAllowed = async function (where, user) {
|
|||
})
|
||||
}
|
||||
|
||||
ApikeyModel.prototype.removeIfAllowed = async function (where, user) {
|
||||
if (!this.User.authenticatedUser) await this.User.loadAuthenticatedUser(user)
|
||||
await this.read(where)
|
||||
if (!this.record) return this.setResponse(404, 'apikeyNotFound')
|
||||
if (this.record.userId !== this.User.authenticatedUser.id) {
|
||||
// Not own key - only admin can do that
|
||||
if (this.User.authenticatedUser.role !== 'admin') {
|
||||
return this.setResponse(400, 'permissionLackingToRemoveOtherApiKey')
|
||||
}
|
||||
}
|
||||
await this.remove(where)
|
||||
|
||||
return this.setResponse(204)
|
||||
}
|
||||
|
||||
ApikeyModel.prototype.read = async function (where) {
|
||||
this.record = await this.prisma.apikey.findUnique({ where })
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
ApikeyModel.prototype.remove = async function (where) {
|
||||
await this.prisma.apikey.delete({ where })
|
||||
this.record = false
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
ApikeyModel.prototype.create = async function ({ body, user }) {
|
||||
if (Object.keys(body) < 1) return this.setResponse(400, 'postBodyMissing')
|
||||
if (!body.name) return this.setResponse(400, 'nameMissing')
|
||||
|
@ -101,7 +124,7 @@ ApikeyModel.prototype.create = async function ({ body, user }) {
|
|||
name: body.name,
|
||||
level: body.level,
|
||||
secret: asJson(hashPassword(secret)),
|
||||
userId: user._id,
|
||||
userId: user._id || user.userId,
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
|
@ -109,7 +132,7 @@ ApikeyModel.prototype.create = async function ({ body, user }) {
|
|||
return this.setResponse(500, 'createApikeyFailed')
|
||||
}
|
||||
|
||||
return this.setResponse(200, 'success', {
|
||||
return this.setResponse(201, 'created', {
|
||||
apikey: {
|
||||
key: this.record.id,
|
||||
secret,
|
||||
|
|
|
@ -27,4 +27,12 @@ export function apikeyRoutes(tools) {
|
|||
app.get('/whoami/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Apikey.whoami(req, res, tools)
|
||||
)
|
||||
|
||||
// Remove Apikey
|
||||
app.delete('/apikey/:id/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||
Apikey.delete(req, res, tools)
|
||||
)
|
||||
app.delete('/apikey/:id/key', passport.authenticate(...bsc), (req, res) =>
|
||||
Apikey.delete(req, res, tools)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -287,10 +287,10 @@ describe(`${user} Signup flow and authentication`, () => {
|
|||
expiresIn: 60,
|
||||
})
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.equal(200)
|
||||
expect(res.status).to.equal(201)
|
||||
expect(res.type).to.equal('application/json')
|
||||
expect(res.charset).to.equal('utf-8')
|
||||
expect(res.body.result).to.equal(`success`)
|
||||
expect(res.body.result).to.equal(`created`)
|
||||
expect(typeof res.body.apikey.key).to.equal('string')
|
||||
expect(typeof res.body.apikey.secret).to.equal('string')
|
||||
expect(typeof res.body.apikey.expiresAt).to.equal('string')
|
||||
|
@ -300,6 +300,30 @@ describe(`${user} Signup flow and authentication`, () => {
|
|||
})
|
||||
})
|
||||
|
||||
step(`${user} Create API Key with KEY`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.post('/apikey/key')
|
||||
.auth(store.apikey.key, store.apikey.secret)
|
||||
.send({
|
||||
name: 'Test API key with key',
|
||||
level: 4,
|
||||
expiresIn: 60,
|
||||
})
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.equal(201)
|
||||
expect(res.type).to.equal('application/json')
|
||||
expect(res.charset).to.equal('utf-8')
|
||||
expect(res.body.result).to.equal(`created`)
|
||||
expect(typeof res.body.apikey.key).to.equal('string')
|
||||
expect(typeof res.body.apikey.secret).to.equal('string')
|
||||
expect(typeof res.body.apikey.expiresAt).to.equal('string')
|
||||
expect(res.body.apikey.level).to.equal(4)
|
||||
store.apikeykey = res.body.apikey
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
step(`${user} Read API Key with KEY (whoami)`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
|
@ -320,7 +344,7 @@ describe(`${user} Signup flow and authentication`, () => {
|
|||
chai
|
||||
.request(config.api)
|
||||
.get(`/apikey/${store.apikey.key}/key`)
|
||||
.auth(store.apikey.key, store.apikey.secret)
|
||||
.auth(store.apikeykey.key, store.apikeykey.secret)
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.equal(200)
|
||||
expect(res.type).to.equal('application/json')
|
||||
|
@ -335,7 +359,7 @@ describe(`${user} Signup flow and authentication`, () => {
|
|||
step(`${user} Read API Key with JWT`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.get(`/apikey/${store.apikey.key}/jwt`)
|
||||
.get(`/apikey/${store.apikeykey.key}/jwt`)
|
||||
.set('Authorization', 'Bearer ' + store.token)
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.equal(200)
|
||||
|
@ -343,7 +367,18 @@ describe(`${user} Signup flow and authentication`, () => {
|
|||
expect(res.charset).to.equal('utf-8')
|
||||
expect(res.body.result).to.equal(`success`)
|
||||
const checks = ['key', 'level', 'expiresAt', 'name', 'userId']
|
||||
checks.forEach((i) => expect(res.body.apikey[i]).to.equal(store.apikey[i]))
|
||||
checks.forEach((i) => expect(res.body.apikey[i]).to.equal(store.apikeykey[i]))
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
step(`${user} Remove API Key with KEY`, (done) => {
|
||||
chai
|
||||
.request(config.api)
|
||||
.delete(`/apikey/${store.apikeykey.key}/key`)
|
||||
.auth(store.apikeykey.key, store.apikeykey.secret)
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.equal(204)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -7,8 +7,52 @@ import { Tab, Tabs } from './tabs.js'
|
|||
import Example from './example.js'
|
||||
import Examples from './examples.js'
|
||||
|
||||
const methodClasses = {
|
||||
get: 'bg-green-600 text-white',
|
||||
post: 'bg-sky-600 text-white',
|
||||
put: 'bg-orange-500 text-white',
|
||||
delete: 'bg-red-600 text-white',
|
||||
}
|
||||
|
||||
const Method = (props) => {
|
||||
let method = false
|
||||
for (const m in methodClasses) {
|
||||
if (!method && props[m]) method = m.toUpperCase()
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`my-1 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 rounded-full ${
|
||||
methodClasses[method.toLowerCase()]
|
||||
}`}
|
||||
>
|
||||
{method}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const statusClasses = {
|
||||
2: 'bg-green-600 text-white',
|
||||
4: 'bg-orange-500 text-white',
|
||||
5: 'bg-red-600 text-white',
|
||||
}
|
||||
|
||||
const StatusCode = ({ status }) => {
|
||||
return (
|
||||
<div
|
||||
className={`my-1 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 rounded-full ${
|
||||
statusClasses['' + status.slice(0, 1)]
|
||||
}`}
|
||||
>
|
||||
{status}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const mdxCustomComponents = (app = false) => ({
|
||||
// Custom components
|
||||
Method,
|
||||
StatusCode,
|
||||
Comment: (props) => <Popout {...props} comment />,
|
||||
Fixme: (props) => <Popout {...props} fixme />,
|
||||
Link: (props) => <Popout {...props} link />,
|
||||
|
|
|
@ -18,3 +18,4 @@
|
|||
<!-- Background opacity for highlighted lines in code -->
|
||||
<code class="bg-yellow-300 bg-opacity-5" />
|
||||
<code class="bg-orange-300 bg-opacity-5 opacity-80 line-through decoration-orange-500" />
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue