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

feat(lib-auth): @zooniverse/auth package #6376

Closed
wants to merge 11 commits into from
Closed
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ See each package's folder for more specific documentation.
| package name | folder | description |
|---|---|---|
| **@zooniverse/async-states** | `packages/lib-async-states` | Frozen object of async states to use in data stores |
| **@zooniverse/auth** | `packages/lib-auth` | A client for handling auth tokens that works on server and client |
| **@zooniverse/classifier** | `packages/lib-classifier` | Classifier view components and state which can be exported modularly or altogether as a working classifier |
| **@zooniverse/fe-project** | `packages/app-project` | Server-side rendered application for a project (anything at `/projects/owner/display_name`) |
| **@zooniverse/grommet-theme** | `packages/lib-grommet-theme` | The style definitions for a Zooniverse theme to use with Grommet |
Expand Down
1 change: 1 addition & 0 deletions packages/lib-auth/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test/unit/playground.js
11 changes: 11 additions & 0 deletions packages/lib-auth/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Change Log
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [0.0.1] Unpublished

### Added

- Add client to use the OAuth 2 client credentials flow
57 changes: 57 additions & 0 deletions packages/lib-auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# @zooniverse/auth

An authentication library for the Panoptes API which provides tokens for making authenticated requests. It uses [`axios`](https://github.com/axios/axios) for making XHR requests under the hood.

## Example

```js
import createClient from '@zooniverse/auth

const client = createClient({
clientAppID: 'someClientAppID',
hostUrl: 'http://enterprise.com'
})

async function login () {
const user = {
login: '[email protected]'
password: 'TeaEarlGreyHot'
}
return client.signIn(user)
}

// Use this to make an authenticated request, like getting the user object!
const accessToken = await login()
```

## Testing

Tests are kept in the `/test` directory, which allows us to keep all test-related code in one place, rather than having unit tests with the code and functional tests in another directory.

### Unit tests

The unit tests test individual methods, and are kept in the `/test/unit` directory.

```sh
yarn test:unit
```

### Functional tests

The functional (or end-to-end) tests treat the client as a black box, and are kept in the `/test/functional` directory. There are two types of functional testing available:

#### Isolation

The test suite is run against the client in isolation, with requests to the API mocked by `nock`.

```sh
yarn test:functional:node
```

#### Staging

Performs the same functional test suite as `isolation`, but against the live staging API.

```sh
yarn test:functional:node:staging
```
138 changes: 138 additions & 0 deletions packages/lib-auth/docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
## Classes

<dl>
<dt><a href="#Client">Client</a></dt>
<dd><p>The main Client class, which provides methods to authenticate against the API and request access tokens.</p>
</dd>
</dl>

## Functions

<dl>
<dt><a href="#createClient">createClient(config)</a> ⇒ <code>Object</code></dt>
<dd><p>Creates a new Panoptes client</p>
</dd>
</dl>

<a name="Client"></a>

## Client
The main Client class, which provides methods to authenticate against the API and request access tokens.

**Kind**: global class

* [Client](#Client)
* [new Client(config, httpClient)](#new_Client_new)
* [.getAccessToken()](#Client+getAccessToken) ⇒ <code>Promise</code>
* [._getJWTFromResponse(response)](#Client+_getJWTFromResponse)
* [.isSignedIn()](#Client+isSignedIn) ⇒ <code>boolean</code>
* [.register(user)](#Client+register) ⇒ <code>Promise</code>
* [.resumeSession([jwt])](#Client+resumeSession) ⇒ <code>Promise</code>
* [.signIn(credentials)](#Client+signIn) ⇒ <code>Promise</code>
* [.signOut()](#Client+signOut) ⇒ <code>Promise</code>

<a name="new_Client_new"></a>

### new Client(config, httpClient)
Creates a new instance of the client


| Param | Type | Description |
| --- | --- | --- |
| config | <code>object</code> | Configuration parameters for the client |
| httpClient | <code>object</code> | A configured HTTP client for making requests |

<a name="Client+getAccessToken"></a>

### client.getAccessToken() ⇒ <code>Promise</code>
Gets an access token. Returns the current access token if it's still valid, otherwise attempts to try and refresh it before returning.

**Kind**: instance method of [<code>Client</code>](#Client)
**Returns**: <code>Promise</code> - Resolves to the access token, or an empty string if not available.
<a name="Client+_getJWTFromResponse"></a>

### client.\_getJWTFromResponse(response)
Extracts the JWT from a response object's `set-cookie` header.

**Kind**: instance method of [<code>Client</code>](#Client)

| Param | Type | Description |
| --- | --- | --- |
| response | <code>Object</code> | The response object |
| response.headers | <code>Object</code> | The headers on the response object |
| response.headers.set-cookie | <code>Array</code> \| <code>string</code> | The `set-cookie` header |

<a name="Client+isSignedIn"></a>

### client.isSignedIn() ⇒ <code>boolean</code>
Check whether a user is signed in

**Kind**: instance method of [<code>Client</code>](#Client)
<a name="Client+register"></a>

### client.register(user) ⇒ <code>Promise</code>
Registers a new user and logs them in.

**Kind**: instance method of [<code>Client</code>](#Client)
**Returns**: <code>Promise</code> - Resolves to a new access token.

| Param | Type | Description |
| --- | --- | --- |
| user | <code>Object</code> | The details of the new user account. |
| user.betaEmailCommunication | <code>boolean</code> | Whether the new user wants to opt in to beta emails. |
| user.creditedName | <code>string</code> | The new user's credited name (used in citations etc). |
| user.email | <code>string</code> | The new user's email address. |
| user.login | <code>string</code> | The new user's username. |
| user.globalEmailCommunication | <code>boolean</code> | Whether the new user wants to opt in to global emails. |
| user.password | <code>string</code> | The new user's password. |
| user.projectEmailCommunication | <code>boolean</code> | Whether the new user wants to opt in to project-related emails when registering via a specific project. |
| user.projectId | <code>string</code> | The project ID when registering via a specific project. |

<a name="Client+resumeSession"></a>

### client.resumeSession([jwt]) ⇒ <code>Promise</code>
Resumes a session for a logged-in user.

**Kind**: instance method of [<code>Client</code>](#Client)
**Returns**: <code>Promise</code> - Resolves to the new access token if logged in, or `null` if logged out.

| Param | Type | Description |
| --- | --- | --- |
| [jwt] | <code>string</code> | A JWT to exchange for an access token. Allows the client to be used on the server side by extracting the JWT included in the cookies in the `req` object and passing it in here. If run on the browser without the JWT argument, it will try to retrieve it from document object. |

<a name="Client+signIn"></a>

### client.signIn(credentials) ⇒ <code>Promise</code>
Signs a user in

**Kind**: instance method of [<code>Client</code>](#Client)
**Returns**: <code>Promise</code> - Resolves to the new access token.

| Param | Type | Description |
| --- | --- | --- |
| credentials | <code>Object</code> | The credentials for the user logging in. |
| credentials.login | <code>string</code> | The user's username or email address. |
| credentials.password | <code>string</code> | The user's password. |

<a name="Client+signOut"></a>

### client.signOut() ⇒ <code>Promise</code>
Signs a user out.

**Kind**: instance method of [<code>Client</code>](#Client)
**Returns**: <code>Promise</code> - Resolves to `null`.
<a name="createClient"></a>

## createClient(config) ⇒ <code>Object</code>
Creates a new Panoptes client

**Kind**: global function
**Returns**: <code>Object</code> - Client instance.

| Param | Type | Description |
| --- | --- | --- |
| config | <code>Object</code> | the config object to validate. |
| config.clientAppID | <code>string</code> | the client app ID of the API. |
| [config.cookieName] | <code>string</code> | the name of the session cookie to use. Defaults to `_Panoptes_session`. |
| config.hostUrl | <code>string</code> | the URL of the API. |

32 changes: 32 additions & 0 deletions packages/lib-auth/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@zooniverse/auth",
"version": "1.0.0",
"main": "src/index.js",
Copy link
Contributor Author

@eatyourgreens eatyourgreens Oct 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new package was originally written as a CJS package. Nowadays, it would make more sense to write it as an ES Module. CJS has never been supported in browsers, and ESM is well-supported in Node now.

"license": "Apache-2.0",
"dependencies": {
"axios": "~0.19.0",
"cookie": "~0.4.0",
"detect-node": "~2.0.4",
"yup": "~0.27.0"
},
"scripts": {
"docs": "jsdoc2md src/*.js > docs/README.md",
"lint": "zoo-standard",
"lint:fix": "zoo-standard --fix",
"test": "yarn test:unit",
"test:ci": "BABEL_ENV=test yarn test:unit --reporter=min",
"test:functional:node": "mocha ./test/functional/node/isolation/test.js",
"test:functional:node:staging": "mocha ./test/functional/node/staging/test.js",
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
},
"devDependencies": {
"@zooniverse/standard": "~2.0.0",
"axios-debug-log": "~0.6.2",
"chai": "~4.2.0",
"dirty-chai": "~2.0.1",
"jsdoc": "~3.6.3",
"jsdoc-to-markdown": "~5.0.3",
"mocha": "~6.1.4",
"nock": "~11.7.0"
}
}
Loading