Skip to content

Commit

Permalink
feat(Auth0): Add Auth0 Authentication (adrien2p#27)
Browse files Browse the repository at this point in the history
* feat(Auth0): Add Auth0 Authentication

The following PR enables authentication via Auth0.

Required: `auth0Domain` in the MedusaConfig.

* Add Store Authentication

* Update Auth0 Admin and Store authentication

* .

* Rebase and add legacy authentication

* Update README

* .

* Update Authentication Plugin tests

* Update README

* Updated Tests and re-added Legacy authentication default

* .

* Add API Route and Further testing

* Update ReadMe

* .

* Update tests

* .

* .

* .

* .

* .
  • Loading branch information
juanzgc authored Jan 16, 2023
1 parent b5f6d7a commit 06c0c20
Show file tree
Hide file tree
Showing 27 changed files with 1,104 additions and 104 deletions.
1 change: 1 addition & 0 deletions packages/medusa-plugin-auth/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.idea
.vscode

/api
/core
Expand Down
89 changes: 87 additions & 2 deletions packages/medusa-plugin-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,33 @@ Then, in your medusa config plugins collection you can add the following configu
options: {
// Enable google OAuth 2
google: {
// ----------- REQUIRED -----------
clientID: "__YOUR_CLIENT_ID__",
clientSecret: "__YOUR_CLIENT_SECRET__",

// Enable google OAuth 2 for the admin domain
admin: {
// ----------- REQUIRED -----------
callbackUrl:`${process.env.BACKEND_URL}/admin/auth/google/cb`,
failureRedirect: `${process.env.ADMIN_URL}/login`,
successRedirect: `${process.env.ADMIN_URL}/`,

// ----------- OPTIONAL -----------
authPath: '/admin/auth/google',
authCallbackPath: '/admin/auth/google/cb',
expiresIn: 24 * 60 * 60 * 1000
},

// Enable google OAuth 2 for the store domain
store: {
// ----------- REQUIRED -----------
callbackUrl:`${process.env.BACKEND_URL}/store/auth/google/cb`,
failureRedirect: `${process.env.STORE_URL}/login`,
successRedirect: `${process.env.STORE_URL}/`,

// ----------- OPTIONAL -----------
authPath: '/store/auth/google',
authCallbackPath: '/store/auth/google/cb',
expiresIn: 24 * 60 * 60 * 1000
}
}
Expand Down Expand Up @@ -92,20 +105,33 @@ Then, in your medusa config plugins collection you can add the following configu
options: {
// Enable facebook OAuth 2
facebook: {
// ----------- REQUIRED -----------
clientID: "__YOUR_CLIENT_ID__",
clientSecret: "__YOUR_CLIENT_SECRET__",

// Enable facebook OAuth 2 for the admin domain
admin: {
// ----------- REQUIRED -----------
callbackUrl:`${process.env.BACKEND_URL}/admin/auth/facebook/cb`,
failureRedirect: `${process.env.ADMIN_URL}/login`,
successRedirect: `${process.env.ADMIN_URL}/`,

// ----------- OPTIONAL -----------
authPath: '/admin/auth/facebook',
authCallbackPath: '/admin/auth/facebook/cb',
expiresIn: 24 * 60 * 60 * 1000
},

// Enable facebook OAuth 2 for the store domain
store: {
// ----------- REQUIRED -----------
callbackUrl:`${process.env.BACKEND_URL}/store/auth/facebook/cb`,
failureRedirect: `${process.env.STORE_URL}/login`,
successRedirect: `${process.env.STORE_URL}/`,

// ----------- OPTIONAL -----------
authPath: '/store/auth/facebook',
authCallbackPath: '/store/auth/facebook/cb',
expiresIn: 24 * 60 * 60 * 1000
}
}
Expand All @@ -131,7 +157,7 @@ Now you can add your Facebook sign in button in your client with something along

### Linkedin

> By default, the admin only allow to authenticate while the store create a new user of it does not exist yet.
> By default, the admin only allow to authenticate while the store create a new user if it does not exist yet.
> This behaviour can be changed and customised by specifying a custom `verifyCallback` in the configuration.
Then, in your medusa config plugins collection you can add the following configuration and update it according to your requirements ([full configuration here](https://github.com/adrien2p/medusa-plugins/tree/main/packages/medusa-plugin-auth/src/auth-strategies/linkedin/types.ts))
Expand All @@ -142,20 +168,33 @@ Then, in your medusa config plugins collection you can add the following configu
options: {
// Enable linkedin OAuth 2
linkedin: {
// ----------- REQUIRED -----------
clientID: "__YOUR_CLIENT_ID__",
clientSecret: "__YOUR_CLIENT_SECRET__",

// Enable linkedin OAuth 2 for the admin domain
admin: {
// ----------- REQUIRED -----------
callbackUrl:`${process.env.BACKEND_URL}/admin/auth/linkedin/cb`,
failureRedirect: `${process.env.ADMIN_URL}/login`,
successRedirect: `${process.env.ADMIN_URL}/`,

// ----------- OPTIONAL -----------
authPath: '/admin/auth/linkedin',
authCallbackPath: '/admin/auth/linkedin/cb',
expiresIn: 24 * 60 * 60 * 1000
},

// Enable linkedin OAuth 2 for the store domain
store: {
// ----------- REQUIRED -----------
callbackUrl:`${process.env.BACKEND_URL}/store/auth/linkedin/cb`,
failureRedirect: `${process.env.STORE_URL}/login`,
successRedirect: `${process.env.STORE_URL}/`,

// ----------- OPTIONAL -----------
authPath: '/store/auth/linkedin',
authCallbackPath: '/store/auth/linkedin/cb',
expiresIn: 24 * 60 * 60 * 1000
}
}
Expand All @@ -177,7 +216,53 @@ Now you can add your Linkedin sign in button in your client with something along

### Auth0

Coming soon
> By default, the admin only allow to authenticate while the store create a new user of it does not exist yet.
> This behaviour can be changed and customised by specifying a custom `verifyCallback` in the configuration.
Then, in your medusa config plugins collection you can add the following configuration and update it according to your requirements ([full configuration here](https://github.com/adrien2p/medusa-plugins/tree/main/packages/medusa-plugin-auth/src/auth-strategies/auth0/types.ts))

```ts
{
resolve: "medusa-plugin-auth",
options: {
// Enable Auth0
auth0: {
// ----------- REQUIRED -----------
clientID: "__YOUR_CLIENT_ID__",
clientSecret: "__YOUR_CLIENT_SECRET__",
auth0Domain: "__YOUR_AUTH0_DOMAIN__",

// Enable Auth0 for Admin domain
admin: {
// ----------- REQUIRED -----------
callbackUrl: `${process.env.BACKEND_URL}/admin/auth/auth0/cb`,
failureRedirect: `${process.env.ADMIN_URL}/login`,
successRedirect: `${process.env.ADMIN_URL}/`,

// ----------- OPTIONAL -----------
authPath: '/admin/auth/auth0',
authCallbackPath: '/admin/auth/auth0/cb',
expiresIn: 24 * 60 * 60 * 1000,
},

// Enable Auth0 for Store domain
store: {
callbackUrl: `${process.env.BACKEND_URL}/store/auth/auth0/cb`,
failureRedirect: `${process.env.STORE_URL}/login`,
successRedirect: `${process.env.STORE_URL}/`,

// ----------- OPTIONAL -----------
authPath: '/store/auth/auth0',
authCallbackPath: '/store/auth/auth0/cb',
expiresIn: 24 * 60 * 60 * 1000,
}
}
// ...
// ... Other authentication provider options
// ...
}
}
```

### Github

Expand Down
2 changes: 2 additions & 0 deletions packages/medusa-plugin-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"keywords": [
"social",
"auth",
"auth0",
"google",
"google+",
"facebook",
Expand Down Expand Up @@ -64,6 +65,7 @@
"cors": "^2.8.5",
"express": "^4.18.1",
"jsonwebtoken": "^8.5.1",
"passport-auth0": "^1.4.3",
"passport-facebook": "^3.0.0",
"passport-google-oauth2": "^0.2.0",
"passport-linkedin-oauth2": "^2.0.0"
Expand Down
2 changes: 2 additions & 0 deletions packages/medusa-plugin-auth/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import loadConfig from '@medusajs/medusa/dist/loaders/config';
import GoogleStrategy from '../auth-strategies/google';
import FacebookStrategy from '../auth-strategies/facebook';
import LinkedinStrategy from '../auth-strategies/linkedin';
import Auth0Strategy from '../auth-strategies/auth0';

import { AuthOptions } from '../types';

Expand All @@ -18,6 +19,7 @@ function loadRouters(configModule: ConfigModule, options: AuthOptions): Router[]
routers.push(...GoogleStrategy.getRouter(configModule, options));
routers.push(...FacebookStrategy.getRouter(configModule, options));
routers.push(...LinkedinStrategy.getRouter(configModule, options));
routers.push(...Auth0Strategy.getRouter(configModule, options));

return routers;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { ConfigModule, MedusaContainer } from '@medusajs/medusa/dist/types/global';
import { Auth0AdminStrategy } from '../../admin';
import { AUTH_PROVIDER_KEY } from '../../../../types';
import { Auth0Options, AUTH0_ADMIN_STRATEGY_NAME, Profile, ExtraParams } from '../../types';

describe('Auth0 admin strategy verify callback', function () {
const existsEmail = 'exists@test.fr';
const existsEmailWithProviderKey = 'exist3s@test.fr';
const existsEmailWithWrongProviderKey = 'exist4s@test.fr';

let container: MedusaContainer;
let req: Request;
let accessToken: string;
let refreshToken: string;
let profile: Profile;
let extraParams: ExtraParams;
let auth0AdminStrategy: Auth0AdminStrategy;

beforeEach(() => {
profile = {
emails: [{ value: existsEmail }],
};

extraParams = {};

container = {
resolve: (name: string) => {
const container_ = {
userService: {
retrieveByEmail: jest.fn().mockImplementation(async (email: string) => {
if (email === existsEmail) {
return {
id: 'test',
};
}

if (email === existsEmailWithProviderKey) {
return {
id: 'test2',
metadata: {
[AUTH_PROVIDER_KEY]: AUTH0_ADMIN_STRATEGY_NAME
},
};
}

if (email === existsEmailWithWrongProviderKey) {
return {
id: 'test3',
metadata: {
[AUTH_PROVIDER_KEY]: 'fake_provider_key'
},
};
}

return;
}),
},
};

return container_[name];
},
} as MedusaContainer;

auth0AdminStrategy = new Auth0AdminStrategy(
container,
{} as ConfigModule,
{ auth0Domain: 'fakeDomain', clientID: 'fake', clientSecret: 'fake', admin: { callbackUrl: '/fakeCallbackUrl'} } as Auth0Options
);
});

afterEach(() => {
jest.clearAllMocks();
});

it('should succeed', async () => {
profile = {
emails: [{ value: existsEmailWithProviderKey }],
};

const data = await auth0AdminStrategy.validate(req, accessToken, refreshToken, extraParams, profile);
expect(data).toEqual(
expect.objectContaining({
id: 'test2',
})
);
});

it('should fail when a user exists without the auth provider metadata', async () => {
profile = {
emails: [{ value: existsEmail }],
};

const err = await auth0AdminStrategy.validate(req, accessToken, refreshToken, extraParams, profile).catch((err) => err);
expect(err).toEqual(new Error(`Admin with email ${existsEmail} already exists`));
});

it('should fail when a user exists with the wrong auth provider key', async () => {
profile = {
emails: [{ value: existsEmailWithWrongProviderKey }],
};

const err = await auth0AdminStrategy.validate(req, accessToken, refreshToken, extraParams, profile).catch((err) => err);
expect(err).toEqual(new Error(`Admin with email ${existsEmailWithWrongProviderKey} already exists`));
});

it('should fail when the user does not exist', async () => {
profile = {
emails: [{ value: 'fake' }],
};

const err = await auth0AdminStrategy.validate(req, accessToken, refreshToken, extraParams, profile).catch((err) => err);
expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`));
});
});
Loading

0 comments on commit 06c0c20

Please sign in to comment.