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

Imprv/use properly user public fields #2667

Merged
merged 13 commits into from Aug 21, 2020

Conversation

itizawa
Copy link
Contributor

@itizawa itizawa commented Aug 18, 2020

No description provided.

@itizawa itizawa self-assigned this Aug 18, 2020
@@ -55,7 +54,6 @@ export default class PersonalContainer extends Container {
isEmailPublished: currentUser.isEmailPublished,
lang: currentUser.lang,
isGravatarEnabled: currentUser.isGravatarEnabled,
isPasswordSet: (currentUser.password != null),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

client には password を渡さない

router.get('/is-password-set', accessTokenParser, loginRequiredStrictly, async(req, res) => {
const isPasswordSet = await User.isPasswordSetById(req.user._id);
return res.apiv3({ isPasswordSet });
});
Copy link
Contributor Author

@itizawa itizawa Aug 18, 2020

Choose a reason for hiding this comment

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

password の有無を使用するのは再設定フォームだけだった

Copy link
Member

Choose a reason for hiding this comment

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

isPasswordSetById を廃止してこっちに持ってきたとしても、まだいろ足りない異常系処理がある。

  • User.findOne に対する try-catch

@itizawa
Copy link
Contributor Author

itizawa commented Aug 18, 2020

before

{"data":{"currentUser":{"isGravatarEnabled":false,"isEmailPublished":true,"lang":"ja_JP","status":2,"admin":true,"_id":"5f39e8865d847b06c1a39c9a","createdAt":"2020-08-17T02:16:38.246Z","name":"admin","username":"admin","email":"admin@gmail.com","password":"642e56804d039a5c2b29f38ff16b705ca94d3c9c087d9965cc0c0dcf3aa3aeb7","__v":0,"imageUrlCached":"/attachment/5f3a050cfc3d5e13dc63fe12","lastLoginAt":"2020-08-17T04:02:19.228Z","imageAttachment":"5f3a050cfc3d5e13dc63fe12"}}}

after

{"data":{"currentUser":{"isGravatarEnabled":false,"isEmailPublished":true,"lang":"ja_JP","admin":true,"_id":"5f39e8865d847b06c1a39c9a","createdAt":"2020-08-17T02:16:38.246Z","name":"admin","username":"admin","email":"admin@gmail.com","imageUrlCached":"/attachment/5f3a050cfc3d5e13dc63fe12","lastLoginAt":"2020-08-17T04:02:19.228Z"}}}

}
const user = await this.findOne({ _id: userId });
return (user.password != null);
};
Copy link
Member

Choose a reason for hiding this comment

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

このメソッドは消そう。

  • この程度の処理(user.password != null)は route でやって差し支えない
  • loginRequiredStrictly をくぐり抜けたという前提があれば findOne の結果が null であることは考慮しなくて良いが、model のメソッドとして用意するならその辺も必要になってくる

router.get('/is-password-set', accessTokenParser, loginRequiredStrictly, async(req, res) => {
const isPasswordSet = await User.isPasswordSetById(req.user._id);
return res.apiv3({ isPasswordSet });
});
Copy link
Member

Choose a reason for hiding this comment

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

isPasswordSetById を廃止してこっちに持ってきたとしても、まだいろ足りない異常系処理がある。

  • User.findOne に対する try-catch

};

userSchema.statics.findUserByApiToken = function(apiToken) {
if (apiToken == null) {
return Promise.resolve(null);
}
return this.findOne({ apiToken });
return this.findOne({ apiToken }).select(this.USER_PUBLIC_FIELDS);
Copy link
Member

Choose a reason for hiding this comment

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

ここで select するという戦略、もうちょっと吟味した方がいい。

悩ましい部分ではある。

現状、どのプロパティを取得するのか、API のレスポンスとして何を消すかは、model を使う側が責任を持つべきであるという考え方から、models/user.js には toObject に transform を設定してあって、様々な service や route で使っている。

こういう stackoverflow もある。
https://stackoverflow.com/questions/17795219/transform-an-array-of-documents-using-mongoose

一方で、user モデルを populate する際に select: User.USER_PUBLIC_FIELDS しているコードがあるが、これらは古いコードだと見なしてよく、なるべく toObject を使うコードにしたい。もし populate するにしても、その処理自体 route or service 側で行うべきである。例外として、populateDataTo* 系メソッドは名が体を表しているので許容する。

Copy link
Member

Choose a reason for hiding this comment

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

補足

model の findByHoge メソッドで select や populate を入れてしまうと、以下のようなデメリットがある

  • もし route, service 層で ORM の組み込みメソッド(Mongoose の findOne など)の利用を許す場合、そちらはそちらで select や populate、transform を備えた toObject の呼び出しを忘れないようにしないといけない
  • にもかかわらず、findByHoge を利用するコードは別途存在し、そちらは model で対策を行っているため、route ,service 層では対策が必要なくなってしまう
  • つまり、同じ route ファイルを眺めたときに、対策を行っているアクションと行っていない(model を信頼して対策を任せている)アクションが並行して存在してしまう
    • これは、route, service 層のアクションのメンテ時の落とし穴になりやすい

以上の理由により、「route, service 層でのみ populate を行う」「route, service 層では、フィールド管理が必須」というルールに一本化した方がメンテナンス性が高くなる。

@@ -418,7 +418,7 @@ module.exports = function(crowi) {
const User = this;

return new Promise((resolve, reject) => {
const query = User.find({ email: emailPartRegExp }, USER_PUBLIC_FIELDS);
const query = User.find({ email: emailPartRegExp });
Copy link
Member

Choose a reason for hiding this comment

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

admin.js の usersSearch が消えたって事は、findUsersByPartOfEmail 自体消してもいいよね?

@itizawa itizawa merged commit 5ffb67f into master Aug 21, 2020
@itizawa itizawa deleted the imprv/use-properly-USER_PUBLIC_FIELDS branch August 21, 2020 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants