Browse Source

feat(api): implement reset password url generation

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/4134/head
Pranav C 2 years ago
parent
commit
39fdbad5af
  1. 2
      packages/nc-gui/components/admin/License.vue
  2. 21
      packages/nc-gui/components/admin/Token.vue
  3. 22
      packages/nc-gui/components/admin/User.vue
  4. 6
      packages/nc-gui/pages/admin/index.vue
  5. 25
      packages/nocodb/src/lib/meta/api/orgUserApis.ts
  6. 2
      packages/nocodb/tests/unit/rest/tests/auth.test.ts
  7. 39
      scripts/sdk/swagger.json

2
packages/nc-gui/components/admin/License.vue

@ -47,7 +47,7 @@ loadTokens()
</script>
<template>
<div class="h-full overflow-y-scroll scrollbar-thin-dull">
<div class="h-full overflow-y-scroll scrollbar-thin-dull pt-4">
<div class="text-xl mt-4">License</div>
<a-divider class="!my-3" />
<a-textarea placeholder="License key" class="!mt-4 max-w-[300px]"></a-textarea>

21
packages/nc-gui/components/admin/Token.vue

@ -93,7 +93,7 @@ const copyToken = (token: string | undefined) => {
</script>
<template>
<div class="h-full overflow-y-scroll scrollbar-thin-dull">
<div class="h-full overflow-y-scroll scrollbar-thin-dull pt-4">
<div class="text-xl mt-4">Token Management</div>
<a-divider class="!my-3" />
<div class="max-w-[900px] mx-auto p-4">
@ -119,14 +119,7 @@ const copyToken = (token: string | undefined) => {
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
</template>
<!-- Description -->
<a-table-column key="description" :title="$t('labels.description')" data-index="description">
<template #default="{ text }">
{{ text }}
</template>
</a-table-column>
<!-- Token -->
<!-- Created By -->
<a-table-column key="created_by" :title="$t('labels.createdBy')" data-index="created_by">
<template #default="{ text }">
<div v-if="text">
@ -136,12 +129,20 @@ const copyToken = (token: string | undefined) => {
</template>
</a-table-column>
<!-- Description -->
<a-table-column key="description" :title="$t('labels.description')" data-index="description">
<template #default="{ text }">
{{ text }}
</template>
</a-table-column>
<!-- Token -->
<a-table-column key="token" :title="$t('labels.token')" data-index="token">
<template #default="{ text, record }">
<div class="w-[320px]">
<span v-if="record.show">{{ text }}</span>
<span v-else>****************************************</span>
<span v-else>*******************************************</span>
</div>
</template>
</a-table-column>

22
packages/nc-gui/components/admin/User.vue

@ -104,10 +104,24 @@ const copyInviteUrl = (user: User) => {
message.success(t('msg.success.inviteURLCopied'))
$e('c:user:copy-url')
}
const copyPasswordResetUrl = async (user: User) => {
try {
const { reset_password_url } = await api.orgUsers.generatePasswordResetToken(user.id)
copy(`${dashboardUrl}/auth/password/reset/${reset_password_url}`)
// Invite URL copied to clipboard
message.success(t('msg.success.inviteURLCopied'))
$e('c:user:copy-url')
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
</script>
<template>
<div class="h-full overflow-y-scroll scrollbar-thin-dull">
<div class="h-full overflow-y-scroll scrollbar-thin-dull pt-4">
<a-tabs v-model:active-key="selectedTabKey" :open-keys="[]" mode="horizontal" class="nc-auth-tabs">
<a-tab-pane v-for="(tab, key) of [{ label: 'Users' }, { label: 'Settings' }]" :key="key" class="select-none">
<template #tab>
@ -232,12 +246,12 @@ const copyInviteUrl = (user: User) => {
<div class="text-xs pl-2">{{ $t('activity.copyInviteURL') }}</div>
</div>
</a-menu-item>
<!-- <a-menu-item>
<div class="flex flex-row items-center py-3" @click="copyInviteUrl(user)">
<a-menu-item>
<div class="flex flex-row items-center py-3" @click="copyPasswordResetUrl(record)">
<MdiContentCopy class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.copyPasswordResetURL') }}</div>
</div>
</a-menu-item> -->
</a-menu-item>
</a-menu>
</template>
</a-dropdown>

6
packages/nc-gui/pages/admin/index.vue

@ -6,9 +6,10 @@ const selectedTabKeys = computed(() => [$route.params.page])
<template>
<div class="container mx-auto h-full">
<a-layout class="mt-3 h-full overflow-y-auto flex">
<a-layout class=" h-full overflow-y-auto flex">
<!-- Side tabs -->
<a-layout-sider>
<a-layout-sider >
<div class="pt-4 h-full bg-white">
<a-menu :selected-keys="selectedTabKeys" class="tabs-menu h-full" :open-keys="[]">
<a-menu-item
key="users"
@ -43,6 +44,7 @@ const selectedTabKeys = computed(() => [$route.params.page])
</div>
</a-menu-item>
</a-menu>
</div>
</a-layout-sider>
<!-- Sub Tabs -->

25
packages/nocodb/src/lib/meta/api/orgUserApis.ts

@ -207,6 +207,26 @@ async function userInviteResend(req, res): Promise<any> {
res.json({ msg: 'success' });
}
async function generateResetUrl(req, res) {
const user = await User.get(req.params.userId);
if (!user) {
NcError.badRequest(`User with id '${req.params.userId}' not found`);
}
const token = uuidv4();
await User.update(user.id, {
email: user.email,
reset_password_token: token,
reset_password_expires: new Date(Date.now() + 60 * 60 * 1000),
token_version: null,
});
res.json({
reset_password_token: token,
reset_password_url: req.ncSiteUrl + `/auth/password/reset/${token}`,
});
}
const router = Router({ mergeParams: true });
router.get(
'/api/v1/users',
@ -238,4 +258,9 @@ router.post(
metaApiMetrics,
ncMetaAclMw(userInviteResend, 'userInviteResend', [OrgUserRoles.SUPER])
);
router.post(
'/api/v1/users/:userId/generate-reset-url',
metaApiMetrics,
ncMetaAclMw(generateResetUrl, 'generateResetUrl', [OrgUserRoles.SUPER])
);
export default router;

2
packages/nocodb/tests/unit/rest/tests/auth.test.ts

@ -4,7 +4,7 @@ import request from 'supertest';
import init from '../../init';
import { defaultUserArgs } from '../../factory/user';
function orgTests() {
function authTests() {
let context;
beforeEach(async function () {

39
scripts/sdk/swagger.json

@ -682,6 +682,45 @@
]
}
},
"/api/v1/users/{userId}/generate-reset-url": {
"parameters": [
{
"schema": {
"type": "string"
},
"name": "userId",
"in": "path",
"required": true
}
],
"post": {
"summary": "Organisation User Generate Password Reset Token",
"operationId": "org-users-generate-password-reset-token",
"tags": [
"Org users"
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"reset_password_token": {
"type": "string"
},
"reset_password_url": {
"type": "string"
}
}
}
}
}
}
}
}
},
"/api/v1/db/meta/projects/{projectId}/users": {
"get": {
"summary": "Project users",

Loading…
Cancel
Save