Skip to content

Commit

Permalink
feat: Allow to pass a custom verify callback for google strategy (adr…
Browse files Browse the repository at this point in the history
…ien2p#18)

* feat: Allow to pass a custon verify callback for google strategy

* chore: Rename pipeline name for the auth plugin

* style: cleanup
  • Loading branch information
adrien2p authored Nov 7, 2022
1 parent 021d06a commit 7b0f824
Show file tree
Hide file tree
Showing 8 changed files with 458 additions and 98 deletions.
41 changes: 41 additions & 0 deletions .github/workflows/medusa-plugin-auth.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: medusa-plugin-auth
on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
unit-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-verion: [16.x]
medusajs-version: [1.3.x, 1.4.x, 1.5.x]
steps:
- name: Cancel Previous Runs
uses: styfle/[email protected]
with:
access_token: ${{ github.token }}

- name: Checkout
uses: actions/[email protected]
with:
fetch-depth: 0

- name: Setup Node.js environment
uses: actions/[email protected]
with:
node-version: ${{ matrix.node-verion }}

- name: 'yarn install'
working-directory: ./packages/medusa-plugin-auth
run: yarn

- name: 'run unit tests'
working-directory: ./packages/medusa-plugin-auth
run: yarn run test:ci
env:
MEDUSAJS_VERSION: ${{ matrix.medusajs-version }}
55 changes: 54 additions & 1 deletion packages/medusa-plugin-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Then, in your medusa config plugins collection you can add the following configu
failureRedirect: `${process.env.ADMIN_URL}/login`,
successRedirect: `${process.env.ADMIN_URL}/`,
authPath: "/admin/auth/google",
authCallbackPath: "/admin/auth/google/cb",
authCallbackPath: "/admin/auth/google/cb",

expiresIn: "24h"
},
Expand All @@ -74,6 +74,59 @@ Then, in your medusa config plugins collection you can add the following configu
}
```

Here is the full configuration types

```typescript

export type AuthOptions = {
google?: {
clientID: string;
clientSecret: string;
admin?: {
callbackUrl: string;
successRedirect: string;
failureRedirect: string;
authPath: string;
authCallbackPath: string;
/**
* The default verify callback function will be used if this configuration is not specified
*/
verifyCallback?: (
container: MedusaContainer,
req: Request,
accessToken: string,
refreshToken: string,
profile: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } },
done: (err: null | unknown, data: null | { id: string }) => void
) => Promise<void>;

expiresIn?: string;
};
store?: {
callbackUrl: string;
successRedirect: string;
failureRedirect: string;
authPath: string;
authCallbackPath: string;
/**
* The default verify callback function will be used if this configuration is not specified
*/
verifyCallback?: (
container: MedusaContainer,
req: Request,
accessToken: string,
refreshToken: string,
profile: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } },
done: (err: null | unknown, data: null | { id: string }) => void
) => Promise<void>;

expiresIn?: string;
};
};
};

```

Now you can add your Google sign in button in your client with something along the lime of the code bellow

```html
Expand Down
23 changes: 11 additions & 12 deletions packages/medusa-plugin-auth/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import loadConfig from '@medusajs/medusa/dist/loaders/config';
import { AUTH_TOKEN_COOKIE_NAME, AuthOptions } from '../types';
import { loadJwtOverrideStrategy } from '../auth-strategies/jwt-override';
import { getGoogleAdminAuthRouter, getGoogleStoreAuthRouter } from '../auth-strategies/google';
import cors from "cors";
import cors from 'cors';

export default function (rootDirectory, pluginOptions: AuthOptions): Router[] {
const configModule = loadConfig(rootDirectory) as ConfigModule;
Expand All @@ -32,39 +32,38 @@ function loadRouters(configModule: ConfigModule, options: AuthOptions): Router[]
}
}


return [...routers, getLogoutRouter(configModule)];
}

function getLogoutRouter(configModule: ConfigModule): Router {
const router = Router()
const router = Router();

const logoutHandler = async (req, res) => {
if (req.session) {
req.session.jwt = {}
req.session.destroy()
req.session.jwt = {};
req.session.destroy();
}

res.clearCookie(AUTH_TOKEN_COOKIE_NAME);

res.status(200).json({})
}
res.status(200).json({});
};

const adminCorsOptions = {
origin: configModule.projectConfig.admin_cors.split(','),
credentials: true,
};

router.use("/admin/auth", cors(adminCorsOptions))
router.delete("/admin/auth", wrapHandler(logoutHandler))
router.use('/admin/auth', cors(adminCorsOptions));
router.delete('/admin/auth', wrapHandler(logoutHandler));

const storeCorsOptions = {
origin: configModule.projectConfig.store_cors.split(','),
credentials: true,
};

router.use("/store/auth", cors(storeCorsOptions))
router.delete("/store/auth", wrapHandler(logoutHandler))
router.use('/store/auth', cors(storeCorsOptions));
router.delete('/store/auth', wrapHandler(logoutHandler));

return router;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { verifyAdminCallback } from '../../admin';
import { MedusaContainer } from '@medusajs/medusa/dist/types/global';

describe('Google admin strategy verify callback', function () {
const existsEmail = '[email protected]';

let container: MedusaContainer;
let req: Request;
let accessToken: string;
let refreshToken: string;
let profile: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } };

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

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

return;
}),
},
};

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

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

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

const done = (err, data) => {
expect(data).toEqual(
expect.objectContaining({
id: 'test',
})
);
};

await verifyAdminCallback(container, req, accessToken, refreshToken, profile, done);
});

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

const done = (err) => {
expect(err).toEqual(new Error(`Unable to authenticate the user with the email fake`));
};

await verifyAdminCallback(container, req, accessToken, refreshToken, profile, done);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { verifyStoreCallback } from '../../store';
import { MedusaContainer } from '@medusajs/medusa/dist/types/global';
import { ENTITY_METADATA_KEY } from '../../index';

describe('Google store strategy verify callback', function () {
const existsEmail = '[email protected]';
const existsEmailWithMeta = '[email protected]';

let container: MedusaContainer;
let req: Request;
let accessToken: string;
let refreshToken: string;
let profile: { emails: { value: string }[]; name?: { givenName?: string; familyName?: string } };

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

container = {
resolve: <T>(name: string): T => {
const container_ = {
manager: {
transaction: function (cb) {
cb();
},
},
customerService: {
withTransaction: function () {
return this;
},
create: jest.fn().mockImplementation(async () => {
return { id: 'test' };
}),
retrieveByEmail: jest.fn().mockImplementation(async (email: string) => {
if (email === existsEmail) {
return {
id: 'test',
};
}

if (email === existsEmailWithMeta) {
return {
id: 'test2',
metadata: {
[ENTITY_METADATA_KEY]: true,
},
};
}

return;
}),
},
};

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

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

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

const done = (err, data) => {
expect(data).toEqual(
expect.objectContaining({
id: 'test2',
})
);
};

await verifyStoreCallback(container, req, accessToken, refreshToken, profile, done);
});

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

const done = (err) => {
expect(err).toEqual(new Error(`Customer with email ${existsEmail} already exists`));
};

await verifyStoreCallback(container, req, accessToken, refreshToken, profile, done);
});

it('should succeed and create a new customer if it has not been found', async () => {
profile = {
emails: [{ value: 'fake' }],
name: {
givenName: 'test',
familyName: 'test',
},
};

const done = (err, data) => {
expect(data).toEqual(
expect.objectContaining({
id: 'test',
})
);
};

await verifyStoreCallback(container, req, accessToken, refreshToken, profile, done);
});
});
Loading

0 comments on commit 7b0f824

Please sign in to comment.