@@ -69,7 +69,8 @@ The LibreTexts ADAPT platform is supported by the Department of Education Open Textbook Pilot Project and the California Education Learning Lab. Have questions or - comments? For more information please contact us by email. + comments? For more information please contact us by + email.

For quick navigation, you can use our sitemap. In @@ -80,7 +81,7 @@ target="_blank" > accessibility and our FERPA statement. And you can also view our Terms And Conditions should you @@ -130,6 +131,7 @@ import Navbar from '~/components/Navbar' import { mapGetters } from 'vuex' import Email from '~/components/Email' import Child from '../components/Child.vue' +import linked_accounts from '../pages/settings/linked_accounts.vue' export default { name: 'MainLayout', @@ -139,6 +141,7 @@ export default { Email }, data: () => ({ + linkedAccounts: [], intervalId: null, showFooter: true, skipToContent: '', @@ -172,6 +175,14 @@ export default { } }, mounted () { + this.$nextTick(() => { + this.linkedAccounts = this.user ? JSON.parse(this.user.linked_accounts) : [] + if (this.linkedAccounts.length) { + this.linkedAccounts = this.linkedAccounts.sort((a, b) => { + return a.id === this.user.id ? -1 : b.id === this.user.id ? 1 : 0 + }) + } + }) if (!this.inIFrame && !this.isLearningTreesEditor) { document.getElementById('main-content').style.minHeight = (window.screen.height - 430) + 'px' @@ -198,7 +209,7 @@ export default { } }, methods: { - checkQuestionViewDisplay() { + checkQuestionViewDisplay () { const questionViewDisplay = document.getElementById('questions-loaded') if (questionViewDisplay) { this.showFooter = true diff --git a/resources/js/pages/settings/index.vue b/resources/js/pages/settings/index.vue index c2ae22970..c0dbb4311 100644 --- a/resources/js/pages/settings/index.vue +++ b/resources/js/pages/settings/index.vue @@ -41,7 +41,8 @@ export default { user: 'auth/user' }), tabs () { - return [ + let tabs + tabs = [ { name: this.$t('profile'), route: 'settings.profile' @@ -51,6 +52,10 @@ export default { route: 'settings.password' } ] + if (this.user && this.user.role !== 3) { + tabs.push({ name: 'Linked Accounts', route: 'settings.linked_accounts' }) + } + return tabs } } } diff --git a/resources/js/pages/settings/linked_accounts.vue b/resources/js/pages/settings/linked_accounts.vue new file mode 100644 index 000000000..56ec35bc5 --- /dev/null +++ b/resources/js/pages/settings/linked_accounts.vue @@ -0,0 +1,285 @@ + + + diff --git a/resources/js/router/routes.js b/resources/js/router/routes.js index ff6d51c3d..4871a4c8b 100644 --- a/resources/js/router/routes.js +++ b/resources/js/router/routes.js @@ -306,7 +306,8 @@ let general_paths = [ { path: '', redirect: { name: 'settings.profile' } }, { path: 'profile', name: 'settings.profile', component: page('settings/profile.vue') }, { path: 'password', name: 'settings.password', component: page('settings/password.vue') }, - { path: 'notifications', name: 'settings.notifications', component: page('settings/notifications.vue')} + { path: 'notifications', name: 'settings.notifications', component: page('settings/notifications.vue')}, + { path: 'linked-accounts', name: 'settings.linked_accounts', component: page('settings/linked_accounts.vue')} ] }, { diff --git a/resources/views/emails/link_to_account_validation_code.blade.php b/resources/views/emails/link_to_account_validation_code.blade.php new file mode 100644 index 000000000..20266466d --- /dev/null +++ b/resources/views/emails/link_to_account_validation_code.blade.php @@ -0,0 +1,22 @@ +@extends('beautymail::templates.sunny') + +@section('content') + + @include ('beautymail::templates.sunny.heading' , [ + 'heading' => 'Validation code to Link Accounts', + 'level' => 'h1', + ]) + + @include('beautymail::templates.sunny.contentStart') +

Hi {{$first_name}},

+

+ Please use the following validation code to link your two accounts: {{$validation_code}}. + This code will expire 10 minutes. +

+

If you did not make this request, please contact us.

+

-ADAPT Support

+ @include('beautymail::templates.sunny.contentEnd') + + @include('beautymail::templates.sunny.contentEnd') + +@stop diff --git a/routes/api.php b/routes/api.php index 8781203dd..1e8de116b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -151,9 +151,16 @@ Route::patch('/user/email', 'UserController@updateEmail'); Route::patch('/user/get-user-info-by-email', 'UserController@getUserInfoByEmail'); + + Route::patch('/linked-account/email-validation-code', 'LinkedAccountController@emailLinkToAccountValidationCode'); + Route::patch('/linked-account/validate-code', 'LinkedAccountController@validateCodeToLinkToAccount'); + Route::patch('/linked-account/switch/{account_to_switch_to}', 'LinkedAccountController@switch'); + Route::patch('/linked-account/unlink/{account_to_unlink}', 'LinkedAccountController@unlink'); + Route::post('/user/exit-login-as', 'Auth\UserController@exitLoginAs'); Route::post('/user/login-as-student-in-course', 'Auth\UserController@loginAsStudentInCourse'); + Route::get('/user/get-session', 'Auth\UserController@getSession'); Route::post('/user/instructors-with-public-courses', 'UserController@getInstructorsWithPublicCourses'); Route::get('/user/question-editors', 'UserController@getAllQuestionEditors'); diff --git a/tests/Feature/LinkedAccountsTest.php b/tests/Feature/LinkedAccountsTest.php new file mode 100644 index 000000000..b7eb2cb9a --- /dev/null +++ b/tests/Feature/LinkedAccountsTest.php @@ -0,0 +1,79 @@ +user = factory(User::class)->create(['role' => 2]);//not admin + /* + Route::patch('/linked-account/validate-code', 'LinkedAccountController@validateCodeToLinkToAccount'); + Route::patch('/linked-account/switch/{account_to_switch_to}', 'LinkedAccountController@switch'); + Route::patch('/linked-account/unlink/{account_to_unlink}', 'LinkedAccountController@unlink');*/ + } + + /** @test */ + public function cannot_link_to_own_account() + { + $this->actingAs($this->user) + ->patchJson("/api/linked-account/email-validation-code", ['email' => $this->user->email]) + ->assertJsonValidationErrors('email'); + } + + /** @test */ + public function cannot_link_to_non_instructor_account() + { + $student_user = factory(User::class)->create(['role' => 3]); + $this->actingAs($this->user) + ->patchJson("/api/linked-account/email-validation-code", ['email' => $student_user]) + ->assertJsonValidationErrors('email'); + + } + + /** @test */ + public function cannot_link_to_admin_account() + { + $admin_user = factory(User::class)->create(['role' => 5]); + $this->actingAs($this->user) + ->patchJson("/api/linked-account/email-validation-code", ['email' => $admin_user->email]) + ->assertJsonValidationErrors('email'); + + } + + /** @test */ + public function must_be_correct_validation_code() + { + $this->actingAs($this->user) + ->patchJson("/api/linked-account/validate-code", ['validation_code' => 'code does not exist']) + ->assertJsonValidationErrors('validation_code'); + } + + /** @test */ + public function cannot_switch_to_invalid_account() + { + $user_2 = factory(User::class)->create(); + $this->actingAs($this->user) + ->patchJson("/api/linked-account/switch/$user_2->id") + ->assertJson(['message' => 'You cannot switch to that account.']); + } + + /** @test */ + public function cannot_unlink_from_account_you_are_not_linked_to() + { + $user_2 = factory(User::class)->create(); + $this->actingAs($this->user) + ->patchJson("/api/linked-account/unlink/$user_2->id") + ->assertJson(['message' => 'You are not allowed to unlink that account.']); + } + + +}