Skip to content
Open
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
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ A [Nuxt](https://nuxtjs.org/) module for integrating the [Directus](https://dire

## Who is this for?

This module integrates the [Directus](https://directus.io/) API into your [Nuxt](https://nuxtjs.org/) project and exposes the [Directus JavaScript SDK](https://docs.directus.io/reference/sdk-js.html) under `$directus`. Furthermore, it comes with a complete, ready-to-use authentication middleware that requires minimal configuration. If you are looking for a ready-to-use solution for using Nuxt with Directus, this might be for you.
This module integrates the [Directus](https://directus.io/) API into your [Nuxt](https://nuxtjs.org/) project and exposes the [Directus JavaScript SDK](https://docs.directus.io/reference/sdk/) under `$directus`. Furthermore, it comes with a complete, ready-to-use authentication middleware that requires minimal configuration. If you are looking for a ready-to-use solution for using Nuxt with Directus, this might be for you.

## Installation

Expand Down Expand Up @@ -61,11 +61,8 @@ router: {

directus: {
apiUrl: 'https://api.acme.net', // your API URL
accessTokenCookieName: 'directus_access_token', // the name of the cookie the access_token will be saved in
refreshTokenCookieName: 'directus_refresh_token', // the name of the cookie the refresh_token will be saved in
loginRoute: '/login', // the route containing your login-form
homeRoute: '/', // the route the user will be redirected to after authentication
hideLoginWhenAuthenticated: true, // when set to true, authenticated users will be redirected to homeRoute, when accessing loginRoute
accessTokenCookieName: 'access_token', // the name of the cookie the access_token will be saved in
refreshTokenCookieName: 'refresh_token', // the name of the cookie the refresh_token will be saved in
}
```

Expand All @@ -75,7 +72,7 @@ This module will expose two new APIs on your context object: `$directus` and `$a

### `$directus`

You can directly access a pre-configured instance of the [Directus SDK](https://docs.directus.io/reference/sdk-js.html) through `this.$directus`.
You can directly access a pre-configured instance of the [Directus SDK](https://docs.directus.io/reference/sdk/) through `this.$directus`.

### `$auth`

Expand Down
12 changes: 0 additions & 12 deletions lib/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,5 @@ Middleware.auth = function ({ store, redirect, route, $cookies }) {
$cookies.remove(options.accessTokenCookieName);
$cookies.remove(options.refreshTokenCookieName);
store.commit('auth/SET_USER', null);
if (route.path !== options.loginRoute) {
return redirect(options.loginRoute);
}
}

if (options.hideLoginWhenAuthenticated && route.path === options.loginRoute && store.state.auth.user) {
return redirect(options.homeRoute);
}

// If the user is not authenticated
if (!store.state.auth.user && route.path !== options.loginRoute) {
return redirect(options.loginRoute);
}
};
7 changes: 2 additions & 5 deletions lib/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ const { resolve, join } = require('path');
export default function (moduleOptions) {
const options = Object.assign(
{
accessTokenCookieName: 'directus_access_token',
refreshTokenCookieName: 'directus_refresh_token',
loginRoute: '/login',
homeRoute: '/',
hideLoginWhenAuthenticated: true,
accessTokenCookieName: 'access_token',
refreshTokenCookieName: 'refresh_token',
},
this.options.directus,
moduleOptions
Expand Down
175 changes: 119 additions & 56 deletions lib/plugin.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import DirectusSDK from '@directus/sdk-js';
import Vue from 'vue';
import jwtDecode from 'jwt-decode';
import createAuthRefreshInterceptor from 'axios-auth-refresh';
import { Directus, BaseStorage, Auth, AxiosTransport } from '@directus/sdk';

export default async (ctx, inject) => {
const options = <%= JSON.stringify(options, null, 2) %>;
Expand All @@ -24,43 +24,46 @@ export default async (ctx, inject) => {
}
ctx.store.registerModule('auth', authModule, opts);

const directus = new DirectusSDK(options.apiUrl, {
auth: {
storage: {
getItem: (key) => {
const data = ctx.$cookies.get(key);
return Promise.resolve(data)
},
setItem: (key, value) => {
ctx.$cookies.set(key, value);
return Promise.resolve();
},
},
mode: 'json',
autoRefresh: false,
const storage = new PluginStorage(null, ctx, options);
const transport = new AxiosTransport(options.apiUrl, storage, async () => {
await auth.refresh();
});
const auth = new Auth(transport, storage, {
mode: 'json',
refresh: {
auto: false,
},
});

const directus = new Directus(options.apiUrl, {
storage,
auth,
transport,
});
inject('directus', directus);
inject('auth', new Auth(ctx, options));
inject('auth', new PluginAuth(ctx, options));

const storedToken = ctx.$cookies.get(options.accessTokenCookieName);
if (storedToken) {
const tokenData = jwtDecode(storedToken);
const validFor = tokenData.exp * 1000 - Date.now();
if (validFor < 300000) {
const { data } = await directus.auth.refresh();
directus.auth.token = data.access_token;
if (data) {
storage.auth_token = data[options.accessTokenCookieName];
}
} else {
directus.auth.token = storedToken;
directus.auth_token = storedToken;
await directus.auth.refresh();
}
if (process.client) {
setTimeout(() => {
ctx.$auth.refresh();
}, ctx.$auth._getTimeUntilRefreshNeeded(directus.auth.token));
}, ctx.$auth._getTimeUntilRefreshNeeded(directus.auth_token));
}
try {
const { data } = await directus.users.me.read();
const data = await directus.users.me.read({
fields: '*,languages.id,languages.languages_code'
});
ctx.store.commit('auth/SET_USER', data);
} catch (error) {
ctx.$cookies.remove(options.accessTokenCookieName);
Expand All @@ -69,17 +72,16 @@ export default async (ctx, inject) => {
}
};

class Auth {
class PluginAuth {
constructor(ctx, options) {
this.options = options;
this.$directus = ctx.$directus;
this.$store = ctx.store;
this.$router = ctx.app.router;
this.$cookies = ctx.$cookies;
this.refreshTimer = null;

createAuthRefreshInterceptor(this.$directus.axios, failedRequest => {
if (process.client && this.$cookies.get('directus_refresh_token')) {
createAuthRefreshInterceptor(this.$directus.transport.axios, failedRequest => {
if (process.client && this.$cookies.get(this.options.refreshTokenCookieName)) {
return this.refresh().then((newToken) => {
failedRequest.response.config.headers['Authorization'] = `Bearer ${newToken}`;
return Promise.resolve();
Expand All @@ -95,52 +97,45 @@ class Auth {
}

async login(credentials) {
try {
const loginData = await this.$directus.auth.login(credentials);
this.$cookies.set(this.options.accessTokenCookieName, loginData.data.access_token);
const { data } = await this.$directus.users.me.read();
await this.$store.commit('auth/SET_USER', data);
this.refreshTimer = setTimeout(() => {
this.refresh();
}, this._getTimeUntilRefreshNeeded(loginData.data.access_token));
this.$router.push(this.options.homeRoute);
} catch (error) {
console.error(error);
const authError = new Error('AuthError');
authError.message = 'Authentication Failure';
authError.data = error.response.data.errors;
throw authError;
}
const loginData = await this.$directus.auth.login(credentials);
this.$cookies.set(this.options.accessTokenCookieName, loginData.access_token);
const data = await this.$directus.users.me.read({
fields: '*,languages.id,languages.languages_code'
});
await this.$store.commit('auth/SET_USER', data);
this.refreshTimer = setTimeout(() => {
this.refresh();
}, this._getTimeUntilRefreshNeeded(loginData.access_token));

return data;
}

async logout() {
await this.$directus.axios.post('/auth/logout', {
refresh_token: this.$cookies.get(this.options.refreshTokenCookieName),
});
await this.$directus.auth.logout();
if (process.client) {
this.refreshTimer = clearTimeout(this.refreshTimer);
}
this.$cookies.remove(this.options.accessTokenCookieName);
this.$cookies.remove(this.options.refreshTokenCookieName);
await this.$store.commit('auth/SET_USER', null);
this.$router.push(this.options.loginRoute);
}

async refresh() {
this.refreshTimer = clearTimeout(this.refreshTimer);
const response = await this.$directus.axios.post('/auth/refresh', {
refresh_token: this.$cookies.get('directus_refresh_token')
},
{
skipAuthRefresh: true,
headers: {
Authorization: ''
const response = await this.$directus.transport.axios.post('/auth/refresh', {
refresh_token: this.$cookies.get(this.options.refreshTokenCookieName),
},
{
skipAuthRefresh: true,
headers: {
Authorization: '',
},
}
});
);

this.$cookies.set('directus_refresh_token', response.data.data.refresh_token);
this.$cookies.set('directus_access_token', response.data.data.access_token);
this.$directus.auth.token = response.data.data.access_token;
this.$cookies.set(this.options.refreshTokenCookieName, response.data.data.refresh_token);
this.$cookies.set(this.options.accessTokenCookieName, response.data.data.access_token);
this.$directus.auth_token = response.data.data.access_token;
this.refreshTimer = setTimeout(() => {
this.refresh();
}, this._getTimeUntilRefreshNeeded(response.data.data.access_token));
Expand All @@ -152,3 +147,71 @@ class Auth {
return validUntil * 1000 - Date.now() - 300000;
}
}

class PluginStorage extends BaseStorage {
constructor(prefix = '', ctx, options) {
super();
this.prefix = prefix;
this.options = options;
this.ctx = ctx;
}

get(key) {
const data = this.ctx.$cookies.get(key);
if (data !== null) {
return data;
}
return null;
}

set(key, value) {
this.ctx.$cookies.set(key, value);
return value;
}

delete(key) {
const value = this.get(key);
this.ctx.$cookies.remove(key);
return value;
}

get auth_token() {
return this.get(this.options.accessTokenCookieName);
}

set auth_token(value) {
if (value === null) {
this.delete(this.options.accessTokenCookieName);
} else {
this.set(this.options.accessTokenCookieName, value);
}
}

get auth_expires() {
const value = this.get('auth_expires');
if (value === null) {
return null;
}
return parseInt(value);
}

set auth_expires(value) {
if (value === null) {
this.delete('auth_expires');
} else {
this.set('auth_expires', value.toString());
}
}

get auth_refresh_token() {
return this.get(this.options.refreshTokenCookieName);
}

set auth_refresh_token(value) {
if (value === null) {
this.delete(this.options.refreshTokenCookieName);
} else {
this.set(this.options.refreshTokenCookieName, value);
}
}
}
Loading