Fix external auth email/password update
authorChocobozzz <me@florianbigard.com>
Wed, 20 May 2020 08:04:44 +0000 (10:04 +0200)
committerChocobozzz <me@florianbigard.com>
Wed, 20 May 2020 08:17:27 +0000 (10:17 +0200)
Also check if an actor does not already exist when creating the user

client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html
client/src/app/+my-account/my-account-settings/my-account-settings.component.html
server/lib/oauth-model.ts
server/middlewares/validators/users.ts
server/tests/api/check-params/users.ts
server/tests/api/videos/video-imports.ts
server/tests/plugins/external-auth.ts
shared/extra-utils/users/users.ts

index f39f66696c96e7caf274178554613c0dd42b34fa..ce176d68294376f3baca3acd742eeee7649f848e 100644 (file)
@@ -9,7 +9,7 @@
   <span class="email">{{ user.pendingEmail }}</span> is awaiting email verification
 </div>
 
-<form role="form" class="change-email" (ngSubmit)="changeEmail()" [formGroup]="form">
+<form role="form" class="change-email" (ngSubmit)="changeEmail()" [formGroup]="form" *ngIf="user.pluginAuth === null">
 
   <div class="form-group">
     <label i18n for="new-email">New email</label>
@@ -23,6 +23,7 @@
   </div>
 
   <div class="form-group">
+    <label i18n for="new-email">Your current password</label>
     <input
       type="password" id="password" i18n-placeholder placeholder="Your password" autocomplete="off"
       formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }" class="form-control"
index f1c4665456d82163876321fca5151871cc394700..b4e4d29f0698d709870c54e4e8f14d2ebf0c5568 100644 (file)
@@ -58,7 +58,7 @@
   </div>
 </div>
 
-<div class="form-row mt-5"> <!-- password grid -->
+<div class="form-row mt-5" *ngIf="user.pluginAuth === null"> <!-- password grid -->
   <div class="form-group col-12 col-lg-4 col-xl-3">
     <div i18n class="account-title">PASSWORD</div>
   </div>
index e5ea4636ee8b2e83dfc3c18afc74a29da4e9a3c8..db546efb16c10deaa043ab8de977e2a66d861c59 100644 (file)
@@ -14,6 +14,7 @@ import { UserAdminFlag } from '@shared/models/users/user-flag.model'
 import { createUserAccountAndChannelAndPlaylist } from './user'
 import { UserRole } from '@shared/models/users/user-role'
 import { PluginManager } from '@server/lib/plugins/plugin-manager'
+import { ActorModel } from '@server/models/activitypub/actor'
 
 type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date }
 
@@ -109,6 +110,9 @@ async function getUser (usernameOrEmail?: string, password?: string) {
     let user = await UserModel.loadByEmail(obj.user.email)
     if (!user) user = await createUserFromExternal(obj.pluginName, obj.user)
 
+    // Cannot create a user
+    if (!user) throw new AccessDeniedError('Cannot create such user: an actor with that name already exists.')
+
     // If the user does not belongs to a plugin, it was created before its installation
     // Then we just go through a regular login process
     if (user.pluginAuth !== null) {
@@ -208,6 +212,10 @@ async function createUserFromExternal (pluginAuth: string, options: {
   role: UserRole
   displayName: string
 }) {
+  // Check an actor does not already exists with that name (removed user)
+  const actor = await ActorModel.loadLocalByName(options.username)
+  if (actor) return null
+
   const userToCreate = new UserModel({
     username: options.username,
     password: null,
index 840b9fc744379e2a13039efafeb5d31b98824e30..3bdbcdf6a349cdcad74ced6392be1aebf6b2ddf5 100644 (file)
@@ -234,14 +234,19 @@ const usersUpdateMeValidator = [
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
 
+    const user = res.locals.oauth.token.User
+
     if (req.body.password || req.body.email) {
+      if (user.pluginAuth !== null) {
+        return res.status(400)
+                  .json({ error: 'You cannot update your email or password that is associated with an external auth system.' })
+      }
+
       if (!req.body.currentPassword) {
         return res.status(400)
                   .json({ error: 'currentPassword parameter is missing.' })
-                  .end()
       }
 
-      const user = res.locals.oauth.token.User
       if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
         return res.status(401)
                   .json({ error: 'currentPassword is invalid.' })
index 4d597f0a3d53a2502af95c98a96256fc427309cc..6e737af15dcb583137b779163c3f9afe72547ed6 100644 (file)
@@ -1044,7 +1044,7 @@ describe('Test users API validators', function () {
       }
       await importVideo(server.url, server.accessToken, immutableAssign(baseAttributes, { targetUrl: getYoutubeVideoUrl() }))
       await importVideo(server.url, server.accessToken, immutableAssign(baseAttributes, { magnetUri: getMagnetURI() }))
-      await importVideo(server.url, server.accessToken, immutableAssign(baseAttributes, { torrentfile: 'video-720p.torrent' }))
+      await importVideo(server.url, server.accessToken, immutableAssign(baseAttributes, { torrentfile: 'video-720p.torrent' as any }))
 
       await waitJobs([ server ])
 
index 4d5989f43995e25a377e877f56e2528f4ae20510..d211859e401ca191aee42f8d1c9d7078182c22bd 100644 (file)
@@ -175,7 +175,7 @@ Ajouter un sous-titre est vraiment facile`)
 
     {
       const attributes = immutableAssign(baseAttributes, {
-        torrentfile: 'video-720p.torrent',
+        torrentfile: 'video-720p.torrent' as any,
         description: 'this is a super torrent description',
         tags: [ 'tag_torrent1', 'tag_torrent2' ]
       })
index a85672782892a0936460436ac6ef3f41d464136a..57361be05a84763b70c71a84b4b03b05b4129fe5 100644 (file)
@@ -255,6 +255,16 @@ describe('Test external auth plugins', function () {
     expect(body.role).to.equal(UserRole.USER)
   })
 
+  it('Should not update an external auth email', async function () {
+    await updateMyUser({
+      url: server.url,
+      accessToken: cyanAccessToken,
+      email: 'toto@example.com',
+      currentPassword: 'toto',
+      statusCodeExpected: 400
+    })
+  })
+
   it('Should reject token of Kefka by the plugin hook', async function () {
     this.timeout(10000)
 
index 54b506bce04810bcc0fd55e52605fe1ce42c25dd..08b7743a6b48f0fd0cbe518b49563a17f7e49ba8 100644 (file)
@@ -216,7 +216,7 @@ function unblockUser (url: string, userId: number | string, accessToken: string,
     .expect(expectedStatus)
 }
 
-function updateMyUser (options: { url: string, accessToken: string } & UserUpdateMe) {
+function updateMyUser (options: { url: string, accessToken: string, statusCodeExpected?: number } & UserUpdateMe) {
   const path = '/api/v1/users/me'
 
   const toSend: UserUpdateMe = omit(options, 'url', 'accessToken')
@@ -226,7 +226,7 @@ function updateMyUser (options: { url: string, accessToken: string } & UserUpdat
     path,
     token: options.accessToken,
     fields: toSend,
-    statusCodeExpected: 204
+    statusCodeExpected: options.statusCodeExpected || 204
   })
 }