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: api token, default token #326

Closed
wants to merge 2 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
26 changes: 24 additions & 2 deletions docs/content/2.setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export default {
}
```


## Options

Defaults:
Expand All @@ -37,7 +36,11 @@ Defaults:
prefix: '/api',
version: 'v4',
cookie: {},
cookieName: 'strapi_jwt'
cookieName: 'strapi_jwt',
auth: {},
apiToken: process.env.STRAPI_API_TOKEN || '',
defaultToken: 'user',
devtools: false
}
```

Expand Down Expand Up @@ -111,6 +114,25 @@ Configure the `fields` query param of the `/users/me` route.

> Learn more on [Field Selection documentation](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest/populating-fields.html#field-selection).

### `apiToken`

::badge
v1.9.0+
::

API token used for your requests. When provided, this will be used when the jwt token is unavailable or [`defaultToken`](#defaulttoken) is set to `api`.

Environment variable `STRAPI_API_TOKEN` can be used to override this option.

> Learn more on [API Tokens](https://docs.strapi.io/dev-docs/configurations/api-tokens).
### `defaultToken`

::badge
v1.9.0+
::

Default token used for the [`useStrapi`](/usage#usestrapi) and [`useStrapiClient`](/usage#usestrapiclient) composables. Value can be either `user` or `api`.

### `devtools`

::badge
Expand Down
26 changes: 24 additions & 2 deletions docs/content/3.usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ All examples below are demonstrated with http calls in script setup. However, to
When using the composable, you can pass in a default data model for all methods.

```ts
const { find } = useStrapi<Course>()
const { findOne } = useStrapi<Course>()

// typed to Course
findOne('courses', '123')
Expand All @@ -34,12 +34,30 @@ findOne('courses', '123')
If you prefer not to use a default data type and want to override the default, you can pass the data model on individual methods as well.

```ts
const { find } = useStrapi<Course>()
const { findOne } = useStrapi<Course>()

// typed to SpecialCourse
findOne<SpecialCourse>('courses', '123')
```

### Options

You can pass an object to the composable to override defaults.

```ts
const config = useRuntimeConfig()
const options = {
token: config.customToken
}
const { findOne } = useStrapi<Course>(options)
// This will now use your customToken for requests.
findOne('courses', '123')
```

::alert{type="warning"}
Currently the only option you can override is `token`.
::

### `find`

Get entries. Returns entries matching the query filters (see [parameters](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest-api.html#api-parameters) documentation).
Expand Down Expand Up @@ -244,6 +262,10 @@ const restaurant = await client<Restaurant>('/restaurants', { method: 'POST', bo
</script>
```

::alert{type="info"}
You can pass the same [`options`](#options) object to this composable as you can in `useStrapi`.
::

## `useStrapiUrl`

This composable is an helper to get the strapi url endpoint. It is used internally to reach the api in the `useStrapiClient` composable.
Expand Down
22 changes: 21 additions & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ export interface ModuleOptions {
*/
auth?: AuthOptions

/**
* Strapi API Token
* @default ''
* @type string | undefined
*/
apiToken?: string,

/**
* Default Token for Requests
* @default 'user'
* @type 'api' | 'user'
*/
defaultToken?: 'api' | 'user',

/**
* Add Strapi Admin in Nuxt Devtools
*
Expand All @@ -78,11 +92,17 @@ export default defineNuxtModule<ModuleOptions>({
prefix: '/api',
version: 'v4',
cookie: {},
auth: {},
cookieName: 'strapi_jwt',
auth: {},
apiToken: process.env.STRAPI_API_TOKEN || '',
defaultToken: 'user',
devtools: false
},
setup (options, nuxt) {
if (options.defaultToken === 'api' && !options.apiToken) {
throw new Error('Missing `STRAPI_API_TOKEN` in `.env`')
}

// Default runtimeConfig
nuxt.options.runtimeConfig.public.strapi = defu(nuxt.options.runtimeConfig.public.strapi, options)
nuxt.options.runtimeConfig.strapi = defu(nuxt.options.runtimeConfig.strapi, options)
Expand Down
6 changes: 4 additions & 2 deletions src/runtime/composables-v3/useStrapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface StrapiV3Client<T> {
delete<F = T>(contentType: string, id?: string | number): Promise<F>
}

export const useStrapi = <T>(): StrapiV3Client<T> => {
return useStrapi3()
export const useStrapi = <T>(options?: {
token?: string
}): StrapiV3Client<T> => {
return useStrapi3(options)
}
6 changes: 4 additions & 2 deletions src/runtime/composables-v3/useStrapi3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { useStrapiVersion, useStrapiClient } from '#imports'
/**
* @deprecated use `useStrapi` for correct types
*/
export const useStrapi3 = () => {
const client = useStrapiClient()
export const useStrapi3 = (options?: {
token?: string
}) => {
const client = useStrapiClient(options)
const version = useStrapiVersion()
if (version !== 'v3') {
// eslint-disable-next-line no-console
Expand Down
6 changes: 4 additions & 2 deletions src/runtime/composables-v4/useStrapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ interface StrapiV4Client<T> {
delete<F = T>(contentType: string, id?: string | number): Promise<Strapi4ResponseSingle<F>>
}

export const useStrapi = <T>(): StrapiV4Client<T> => {
return useStrapi4()
export const useStrapi = <T>(options?: {
token?: string
}): StrapiV4Client<T> => {
return useStrapi4(options)
}
6 changes: 4 additions & 2 deletions src/runtime/composables-v4/useStrapi4.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { useStrapiVersion, useStrapiClient } from '#imports'
/**
* @deprecated use `useStrapi` for correct types
*/
export const useStrapi4 = () => {
const client = useStrapiClient()
export const useStrapi4 = (options?: {
token?: string
}) => {
const client = useStrapiClient(options)
const version = useStrapiVersion()
if (version !== 'v4') {
// eslint-disable-next-line no-console
Expand Down
17 changes: 12 additions & 5 deletions src/runtime/composables/useStrapiClient.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { FetchError, FetchOptions } from 'ohmyfetch'
import { stringify } from 'qs'
import { useNuxtApp } from '#app'
import { useNuxtApp, useRuntimeConfig } from '#app'
import type { Strapi4Error } from '../types/v4'
import type { Strapi3Error } from '../types/v3'
import { useStrapiUrl } from './useStrapiUrl'
Expand All @@ -23,17 +23,24 @@ const defaultErrors = (err: FetchError) => ({
}
})

export const useStrapiClient = () => {
export const useStrapiClient = (options?: {
token?: string
}) => {
const nuxt = useNuxtApp()
const baseURL = useStrapiUrl()
const version = useStrapiVersion()
const token = useStrapiToken()
const userToken = useStrapiToken()
const config = useRuntimeConfig()

return async <T> (url: string, fetchOptions: FetchOptions = {}): Promise<T> => {
const headers: HeadersInit = {}

if (token && token.value) {
headers.Authorization = `Bearer ${token.value}`
if (options?.token) {
headers.Authorization = `Bearer ${options.token}`
} else if (config.strapi.defaultToken === 'user' && userToken.value) {
headers.Authorization = `Bearer ${userToken.value}`
} else if ((config.strapi.defaultToken === 'api' && config.strapi.apiToken) || config.strapi.apiToken) {
Copy link
Member

Choose a reason for hiding this comment

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

Are you sure defaulting to the config.strapi.apiToken is the right thing to do here? For example if I set an apiToken to use in a specific section of my app, I might not want to use it all the time when a user is logged out.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@benjamincanac I wan't entirely sure which way to go with it. My thinking with it this way was trying to avoid cluttering the module config with too many options considering that a token can always be passed to the composable(s).

But, I was considering maybe adding an additional boolean config (privateApi) or something) to allow someone to set whether to use the apiToken in their config as the fall back if the jwt isn't set. This might be beneficial to someone who doesn't want their API public. I'm not against that, if that works for you. It could be this instead:

    headers.Authorization = `Bearer ${options.token}`
    } else if (config.strapi.defaultToken === 'user' && userToken.value) {
      headers.Authorization = `Bearer ${userToken.value}`
    } else if ((config.strapi.defaultToken === 'api' && config.strapi.apiToken) || (config.strapi.privateApi === true && config.strapi.apiToken) {

I'm not sure that privateApi is the best name for that, but just a spur of the moment idea.

Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering, why would you use an api token in your app instead of just using a public endpoint? As this http call will be client-side, all users will be able to see this token so it comes to the same thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, yeah. Potentially, a bit of a misconception on my part. I'm sure you would know better than I. Most of my understanding is just from some articles rather than a wealth of experience. Such as:

https://cloud.google.com/endpoints/docs/openapi/when-why-api-key#:~:text=API%20keys%20provide%20project%20authorization,-To%20decide%20which&text=By%20identifying%20the%20calling%20project,or%20enabled%20in%20the%20API.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry it took so long to catch up, but the only use-case I see to use api tokens in nuxt is if it's used server-side as these tokens are private. However, as mentioned here #320 (comment), the module does not export server functions as of right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No worries. I appreciate you bearing with me as I get up to speed myself :) What you're saying makes total sense. I think there might still be something of value here in terms of customizing the default behavior of the composables. Not in relation to api tokens, but rather maybe not passing the jwt to particular instances of useStrapi and useStrapiClient? Or maybe not, lol. It's super late here and I think my brain is giving up for the night. I'll close this PR for now and give it another think tomorrow. Thanks again!

headers.Authorization = `Bearer ${config.strapi.apiToken}`
}

// Map params according to strapi v3 and v4 formats
Expand Down