Skip to content

Commit

Permalink
feat(Auth): Facebook auth. Closes #138
Browse files Browse the repository at this point in the history
  • Loading branch information
antoinezanardi committed Mar 24, 2021
1 parent 125e269 commit 9d6dfe8
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 14 deletions.
5 changes: 1 addition & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### 🚀 New features

* [#137](https://github.com/antoinezanardi/werewolves-assistant-api/issues/137) - `position` for each player.
* [#138](https://github.com/antoinezanardi/werewolves-assistant-api/issues/138) - Facebook authentication.
* [#141](https://github.com/antoinezanardi/werewolves-assistant-api/issues/141) - Fox role.
* [#142](https://github.com/antoinezanardi/werewolves-assistant-api/issues/142) - Bear Tamer role.
* [#143](https://github.com/antoinezanardi/werewolves-assistant-api/issues/143) - Hidden game repartition option.
Expand All @@ -31,14 +32,10 @@

* [#162](https://github.com/antoinezanardi/werewolves-assistant-api/issues/162) - Role types.

### 🐛 Bug fixes

### ♻️ Refactoring

* [#140](https://github.com/antoinezanardi/werewolves-assistant-api/issues/140) - Extend token's lifetime to infinity.

### 📚 Documentation

### 📦 Packages

* `axios` installed with version `0.21.1`.
Expand Down
7 changes: 6 additions & 1 deletion config/apidoc/footer.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,9 @@ Description for each case below :
| 66 | ALL_PLAYERS_POSITION_NOT_SET | 400 | Some players has a position and other not. You must define all position or none of them. |
| 67 | PLAYERS_POSITION_NOT_UNIQUE | 400 | Players don't all have unique position. |
| 68 | PLAYER_POSITION_TOO_HIGH | 400 | One player's position exceeds the maximum. |
| 69 | THIEF_ADDITIONAL_CARDS_COUNT_NOT_RESPECTED | 400 | Exactly 2 additional cards are needed for thief (depending on game's options). |
| 69 | THIEF_ADDITIONAL_CARDS_COUNT_NOT_RESPECTED | 400 | Exactly 2 additional cards are needed for thief (depending on game's options). |
| 70 | BAD_FACEBOOK_ACCESS_TOKEN | 400 | Access token doesn't allow to get user info. |
| 71 | NEED_FACEBOOK_EMAIL_PERMISSION | 400 | You need to share your email to login with Facebook. |
| 72 | EMAIL_EXISTS_WITH_LOCAL_REGISTRATION | 400 | The email provided already exists with local registration. |
| 73 | EMAIL_EXISTS_WITH_FACEBOOK_REGISTRATION | 400 | The email provided already exists with facebook registration. |
| 74 | EMAIL_EXISTS_WITH_GOOGLE_REGISTRATION | 400 | The email provided already exists with google registration. |
2 changes: 1 addition & 1 deletion config/apidoc/header.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ In order to log in and create games, a user must be created (aka the future game
| _id | ObjectId | User's ID. |
| email | String | User's email. |
| registration | Object | User's registration data. |
|  ⮑ method | String | How the user registered himself. (_Possibilities: `manual`, `facebook` or `google`_) |
|  ⮑ method | String | How the user registered himself. (_Possibilities: `local`, `facebook` or `google`_) |
| createdAt | Date | When the user created his account. |
| updatedAt | Date | When the user updated his account. |

Expand Down
45 changes: 41 additions & 4 deletions src/controllers/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,24 @@ const { checkJWTUserRights } = require("../helpers/functions/User");
const { checkRequestData } = require("../helpers/functions/Express");
const Config = require("../../config");

exports.checkDataBeforeCreate = async data => {
const existingUser = await this.findOne({ email: data.email });
if (existingUser) {
if (existingUser.registration.method === data.registration.method) {
throw generateError("EMAIL_EXISTS", "The email provided already exists.");
}
if (existingUser.registration.method === "local") {
throw generateError("EMAIL_EXISTS_WITH_LOCAL_REGISTRATION", "The email provided already exists with local registration.");
} else if (existingUser.registration.method === "facebook") {
throw generateError("EMAIL_EXISTS_WITH_FACEBOOK_REGISTRATION", "The email provided already exists with facebook registration.");
} else if (existingUser.registration.method === "google") {
throw generateError("EMAIL_EXISTS_WITH_GOOGLE_REGISTRATION", "The email provided already exists with google registration.");
}
}
};

exports.create = async(data, options = {}) => {
await this.checkDataBeforeCreate(data);
const { toJSON } = options;
delete options.toJSON;
if (!Array.isArray(data)) {
Expand Down Expand Up @@ -54,7 +71,7 @@ exports.postUser = async(req, res) => {
try {
const { body } = checkRequestData(req);
await this.generateSaltAndHash(body);
body.registration = { method: "manual" };
body.registration = { method: "local" };
const newUser = await this.create(body, { toJSON: true });
delete newUser.password;
res.status(200).json(newUser);
Expand Down Expand Up @@ -92,6 +109,8 @@ exports.getUser = async(req, res) => {
}
};

exports.getJWT = user => sign({ userId: user._id }, Config.app.JWTSecret);

exports.login = (req, res) => {
try {
checkRequestData(req);
Expand All @@ -103,7 +122,7 @@ exports.login = (req, res) => {
if (loginErr) {
res.status(500).send(loginErr);
}
const token = sign({ userId: user._id }, Config.app.JWTSecret);
const token = this.getJWT(user);
return res.status(200).json({ token });
});
})(req, res);
Expand All @@ -112,11 +131,29 @@ exports.login = (req, res) => {
}
};

exports.getFacebookUser = async accessToken => {
try {
const { data } = await axios.get(`https://graph.facebook.com/me?fields=email&access_token=${accessToken}`);
return data;
} catch (e) {
throw generateError("BAD_FACEBOOK_ACCESS_TOKEN", `Access token "${accessToken}" doesn't allow to get user info.`);
}
};

exports.loginWithFacebook = async(req, res) => {
try {
const { body } = checkRequestData(req);
const { data } = await axios.get(`https://graph.facebook.com/me?fields=email&access_token=${body.accessToken}`);
res.status(200).json(data);
const facebookUser = await this.getFacebookUser(body.accessToken);
if (!facebookUser.email) {
throw generateError("NEED_FACEBOOK_EMAIL_PERMISSION", `You need to share your email to login with Facebook.`);
}
const facebookUserData = { email: facebookUser.email, registration: { method: "facebook" } };
let user = await this.findOne(facebookUserData);
if (!user) {
user = await this.create(facebookUserData);
}
const token = this.getJWT(user);
res.status(200).json({ token });
} catch (e) {
sendError(res, e);
}
Expand Down
2 changes: 1 addition & 1 deletion src/db/schemas/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const userSchema = new Schema({
method: {
type: String,
enum: getRegistrationMethods(),
default: "manual",
default: "local",
},
},
}, {
Expand Down
20 changes: 20 additions & 0 deletions src/helpers/constants/Error.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,24 @@ exports.errorMetadata = {
code: 69,
HTTPCode: 400,
},
BAD_FACEBOOK_ACCESS_TOKEN: {
code: 70,
HTTPCode: 400,
},
NEED_FACEBOOK_EMAIL_PERMISSION: {
code: 71,
HTTPCode: 400,
},
EMAIL_EXISTS_WITH_LOCAL_REGISTRATION: {
code: 72,
HTTPCode: 400,
},
EMAIL_EXISTS_WITH_FACEBOOK_REGISTRATION: {
code: 73,
HTTPCode: 400,
},
EMAIL_EXISTS_WITH_GOOGLE_REGISTRATION: {
code: 74,
HTTPCode: 400,
},
};
2 changes: 1 addition & 1 deletion src/helpers/constants/User.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
exports.registrationMethods = ["manual", "facebook", "google"];
exports.registrationMethods = ["local", "facebook", "google"];
2 changes: 1 addition & 1 deletion src/routes/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ module.exports = app => {
* @apiSuccess {ObjectID} _id User's ID.
* @apiSuccess {String} email User's email.
* @apiSuccess {Object} registration User's registration data.
* @apiSuccess {String} registration.method How the user registered himself. (_Possibilities: `manual`, `facebook` or `google`_)
* @apiSuccess {String} registration.method How the user registered himself. (_Possibilities: `local`, `facebook` or `google`_)
* @apiSuccess {Date} createdAt When the user is created.
* @apiSuccess {Date} updatedAt When the user is updated.
*/
Expand Down
13 changes: 12 additions & 1 deletion tests/e2e/user/user-registration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe("A - Sign up and log in", () => {
.end((err, res) => {
expect(res).to.have.status(200);
expect(res.body.email).to.equal(credentials.email);
expect(res.body.registration.method).to.equal("manual");
expect(res.body.registration.method).to.equal("local");
done();
});
});
Expand Down Expand Up @@ -190,4 +190,15 @@ describe("A - Sign up and log in", () => {
done();
});
});
it("🔐 Can't login with facebook with bad access token (POST /users/login/facebook)", done => {
chai.request(server)
.post(`/users/login/facebook`)
.send({ accessToken: "lol" })
.set({ Authorization: `Bearer ${token}` })
.end((err, res) => {
expect(res).to.have.status(400);
expect(res.body.type).to.equal("BAD_FACEBOOK_ACCESS_TOKEN");
done();
});
});
});

0 comments on commit 9d6dfe8

Please sign in to comment.