Browse Source

Merge pull request #1829 from nocodb/fix/forgot-password

fix: forgot password
pull/1833/head
աɨռɢӄաօռɢ 2 years ago committed by GitHub
parent
commit
c002886e21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      packages/nc-gui/pages/user/password/forgot.vue
  2. 5
      packages/nocodb/src/lib/noco/meta/api/userApi/ui/auth/emailVerify.ts
  3. 7
      packages/nocodb/src/lib/noco/meta/api/userApi/ui/auth/resetPassword.ts
  4. 119
      packages/nocodb/src/lib/noco/meta/api/userApi/ui/auth/signin.ts
  5. 135
      packages/nocodb/src/lib/noco/meta/api/userApi/ui/auth/signup.ts
  6. 425
      packages/nocodb/src/lib/noco/meta/api/userApi/ui/auth/swagger-base.xc.json
  7. 48
      packages/nocodb/src/lib/noco/meta/api/userApi/ui/auth/swagger.ts
  8. 40
      packages/nocodb/src/lib/noco/meta/api/userApi/userApis.ts

19
packages/nc-gui/pages/user/password/forgot.vue

@ -97,23 +97,26 @@ export default {
beforeDestroy() {
},
methods: {
onNormalVerify() {
this.recpatcha = true
},
async resetPasswordHandle(e) {
if (this.$refs.formType.validate()) {
e.preventDefault()
// await this.$recaptchaLoaded()
// const recaptchaToken = await this.$recaptcha('login')
const err = await this.$store.dispatch('users/ActPasswordForgot', { ...this.form })// recaptchaToken});
if (err) {
this.formUtil.formErr = true
this.formUtil.formErrMsg = err.data.msg
} else {
try {
await this.$api.auth.passwordForgot(
{
email: this.form.email
}
)
this.showMsg = true
} catch (e) {
const err = await this._extractSdkResponseErrorMsg(e)
this.formUtil.formErr = true
this.formUtil.formErrMsg = err
return;
}
}
}

5
packages/nocodb/src/lib/noco/meta/api/userApi/ui/auth/emailVerify.ts

@ -7,7 +7,7 @@ export default `<!DOCTYPE html>
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<script src="https://unpkg.com/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.min.js" integrity="sha512-XdUZ5nrNkVySQBnnM5vzDqHai823Spoq1W3pJoQwomQja+o4Nw0Ew1ppxo5bhF2vMug6sfibhKWcNJsG8Vj9tg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
<div id="app">
@ -54,7 +54,7 @@ export default `<!DOCTYPE html>
methods: {},
async created() {
try {
const valid = (await axios.post('<%- baseUrl %>auth/email/validate/' + this.token)).data;
const valid = (await axios.post('<%- baseUrl %>/api/v1/db/auth/email/validate/' + this.token)).data;
this.valid = !!valid;
} catch (e) {
this.valid = false;
@ -74,6 +74,7 @@ export default `<!DOCTYPE html>
*
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
* @author Wing-Kam Wong <wingkwong.code@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*

7
packages/nocodb/src/lib/noco/meta/api/userApi/ui/auth/resetPassword.ts

@ -7,7 +7,7 @@ export default `<!DOCTYPE html>
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<script src="https://unpkg.com/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.min.js" integrity="sha512-XdUZ5nrNkVySQBnnM5vzDqHai823Spoq1W3pJoQwomQja+o4Nw0Ew1ppxo5bhF2vMug6sfibhKWcNJsG8Vj9tg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
<div id="app">
@ -81,7 +81,7 @@ export default `<!DOCTYPE html>
async resetPassword() {
if (this.$refs.form.validate()) {
try {
const res = await axios.post('<%- baseUrl %>auth/password/reset/' + this.token, {
const res = await axios.post('<%- baseUrl %>api/v1/db/auth/password/reset/' + this.token, {
...this.formdata
});
this.success = true;
@ -93,7 +93,7 @@ export default `<!DOCTYPE html>
},
async created() {
try {
const valid = (await axios.post('<%- baseUrl %>auth/token/validate/' + this.token)).data;
const valid = (await axios.post('<%- baseUrl %>api/v1/db/auth/token/validate/' + this.token)).data;
this.valid = !!valid;
} catch (e) {
this.valid = false;
@ -108,6 +108,7 @@ export default `<!DOCTYPE html>
*
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
* @author Wing-Kam Wong <wingkwong.code@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*

119
packages/nocodb/src/lib/noco/meta/api/userApi/ui/auth/signin.ts

@ -1,119 +0,0 @@
export default `<!DOCTYPE html>
<html>
<head>
<title>NocoDB - Sign In</title>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<div id="app">
<v-app>
<v-container>
<v-row class="justify-center">
<v-col class="col-12 col-md-6">
<v-form ref="form" v-model="validForm" ref="formType" class="ma-auto"
lazy-validation>
<v-text-field
name="input-10-2"
label="email"
type="email"
v-model="formdata.email"
:rules="[v => !!v || 'Email is required']"
></v-text-field>
<v-text-field
name="input-10-2"
type="password"
label="Password"
v-model="formdata.password"
:rules="[v => !!v || 'Password is required']"
></v-text-field>
<v-btn
:disabled="!validForm"
large
@click="signin"
>
Sign In
</v-btn>
</v-form>
<br>
<pre style="overflow: auto" v-if="success" v-html="success"></pre>
<v-alert v-else-if="errMsg" type="error">
{{errMsg}}
</v-alert>
</v-col>
</v-row>
</v-container>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
<script>
var app = new Vue({
el: '#app',
vuetify: new Vuetify(),
data: {
valid: null,
validForm: false,
greeting: 'Password Reset',
formdata: {
password: '',
newPassword: ''
},
success: false,
errMsg: null
},
methods: {
async signin() {
if (this.$refs.form.validate()) {
try {
const res = await axios.post('<%- baseUrl %>auth/signin', this.formdata);
this.success = res.data;
} catch (e) {
if (e.response && e.response.data && e.response.data.msg) {
this.errMsg = e.response.data.msg;
} else {
this.errMsg = 'Some error occurred';
}
}
}
}
}
})
</script>
</body>
</html>`;
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

135
packages/nocodb/src/lib/noco/meta/api/userApi/ui/auth/signup.ts

@ -1,135 +0,0 @@
export default `<!DOCTYPE html>
<html>
<head>
<title>NocoDB - Sign Up</title>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<div id="app">
<v-app>
<v-container>
<v-row class="justify-center">
<v-col class="col-12 col-md-6">
<v-form ref="form" v-model="validForm" ref="formType" class="ma-auto"
lazy-validation>
<v-text-field
name="input-10-2"
label="First Name"
type="text"
v-model="formdata.firstname"
:rules="[v => !!v || 'First Name is required']"
></v-text-field>
<v-text-field
name="input-10-2"
label="Last Name"
type="text"
v-model="formdata.lastname"
:rules="[v => !!v || 'Last Name is required']"
></v-text-field>
<v-text-field
name="input-10-2"
label="email"
type="email"
v-model="formdata.email"
:rules="[v => !!v || 'Email is required']"
></v-text-field>
<v-text-field
name="input-10-2"
type="password"
label="Password"
v-model="formdata.password"
:rules="[v => !!v || 'Password is required']"
></v-text-field>
<v-btn
:disabled="!validForm"
large
@click="signin"
>
Sign Up
</v-btn>
</v-form>
<br>
<pre style="overflow: auto" v-if="success" v-html="success"></pre>
<v-alert v-else-if="errMsg" type="error">
{{errMsg}}
</v-alert>
</v-col>
</v-row>
</v-container>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
<script>
var app = new Vue({
el: '#app',
vuetify: new Vuetify(),
data: {
valid: null,
validForm: false,
greeting: 'Password Reset',
formdata: {
password: '',
newPassword: ''
},
success: false,
errMsg: null
},
methods: {
async signin() {
if (this.$refs.form.validate()) {
try {
const res = await axios.post('<%- baseUrl %>auth/signup', this.formdata);
this.success = res.data;
} catch (e) {
if (e.response && e.response.data && e.response.data.msg) {
this.errMsg = e.response.data.msg;
} else {
this.errMsg = 'Some error occurred';
}
}
}
}
}
})
</script>
</body>
</html>`;
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

425
packages/nocodb/src/lib/noco/meta/api/userApi/ui/auth/swagger-base.xc.json

@ -1,425 +0,0 @@
{
"swagger": "2.0",
"info": {
"description": "Create APIs at the speed of your thoughts",
"version": "1.0.0",
"title": "NocoDB",
"contact": {}
},
"host": "localhost:8080",
"basePath": "/",
"tags": [
{
"name": "common"
}
],
"schemes": [
"http"
],
"paths": {
"/auth/signin": {
"post": {
"security": [
],
"tags": [
"Authentication"
],
"summary": "User login",
"description": "",
"operationId": "login",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"in": "body",
"name": "body",
"description": "Authentication user details",
"required": true,
"schema": {
"$ref": "#/definitions/userAuth"
}
}
],
"responses": {
"200": {
"description": "Authenticated successfully",
"schema": {
"$ref": "#/definitions/token"
}
}
}
}
},
"/auth/signup": {
"post": {
"tags": [
"Authentication"
],
"summary": "User signup",
"description": "",
"operationId": "signup",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"in": "body",
"name": "body",
"description": "Signup user details",
"required": true,
"schema": {
"$ref": "#/definitions/user"
}
}
],
"responses": {
"200": {
"description": "Registration success",
"schema": {
"$ref": "#/definitions/token"
}
},
"400": {
"description": "Bad request"
}
}
}
},
"/auth/password/forgot": {
"post": {
"tags": [
"Authentication"
],
"summary": "Password Forgot",
"description": "",
"operationId": "passwordForgot",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"in": "body",
"name": "body",
"description": "Email address",
"required": true,
"schema": {
"type": "object",
"properties": {
"email": {
"type": "string",
"required": true,
"example": "test@nocodb.com"
}
}
}
}
],
"responses": {
"200": {
"description": "Mailed password reset link",
"schema": {
"type": "boolean"
}
}
}
}
},
"/auth/email/validate/{tokenId}": {
"post": {
"tags": [
"Authentication"
],
"summary": "Email validate link",
"description": "",
"operationId": "emailValidate",
"produces": [
"application/json"
],
"parameters": [
{
"name": "tokenId",
"in": "path",
"description": "random token id received",
"required": true,
"type": "string",
"format": "uuid"
}
],
"responses": {
"200": {
"description": "Validated successfully"
}
}
}
},
"/auth/token/validate/{tokenId}": {
"get": {
"tags": [
"Authentication"
],
"summary": "Validate password reset token",
"description": "",
"operationId": "passwordResetTokenValidate",
"produces": [
"application/json"
],
"parameters": [
{
"name": "tokenId",
"in": "path",
"description": "random token id received",
"required": true,
"type": "string",
"format": "uuid"
}
],
"responses": {
"200": {
"description": "Validated successfully"
}
}
}
},
"/auth/password/reset/": {
"post": {
"tags": [
"Authentication"
],
"summary": "Password reset",
"description": "",
"operationId": "passwordReset",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"name": "tokenId",
"in": "path",
"description": "random token id received",
"required": true,
"type": "string",
"format": "uuid"
},
{
"in": "body",
"name": "body",
"description": "Reset password details",
"required": true,
"schema": {
"type": "object",
"properties": {
"password": {
"type": "string",
"format": "password",
"example": "password",
"required": true
}
}
}
}
],
"responses": {
"200": {
"description": "Password reset successfully"
}
}
}
},
"/user/me": {
"get": {
"tags": [
"Authentication"
],
"summary": "User details",
"description": "",
"operationId": "userDetails",
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "User details"
}
}
}
},
"/user": {
"put": {
"tags": [
"Authentication"
],
"summary": "Update user details",
"description": "",
"operationId": "updateUserDetails",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "User details"
}
},
"parameters": [
{
"in": "body",
"name": "body",
"description": "Updated user details",
"required": true,
"schema": {
"$ref": "#/definitions/user"
}
}
]
}
},
"/user/password/change": {
"post": {
"tags": [
"Authentication"
],
"summary": "Update user details",
"description": "",
"operationId": "passwordChange",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "User details"
}
},
"parameters": [
{
"in": "body",
"name": "body",
"description": "Current password and new password",
"required": true,
"schema": {
"type": "object",
"properties": {
"currentPassword": {
"type": "string",
"format": "password",
"example": "password"
},
"newPassword": {
"type": "string",
"format": "password",
"example": "newPassword"
}
}
}
}
]
}
}
},
"definitions": {
"userAuth": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email",
"nullable": false,
"example": "test@nocodb.com"
},
"password": {
"type": "string",
"format": "password",
"nullable": false,
"example": "password"
}
}
},
"token": {
"type": "object",
"properties": {
"token": {
"type": "string",
"format": "email",
"nullable": false,
"example": "< JWT Token >"
}
}
},
"user": {
"allOf": [
{
"$ref": "#/definitions/userAuth"
},
{
"type": "object",
"properties": {
"id": {
"type": "integer",
"nullable": false,
"readOnly": true
},
"firstname": {
"type": "string",
"nullable": false,
"example": "FirstName"
},
"lastname": {
"type": "string",
"nullable": false,
"example": "LastName"
},
"roles": {
"type": "object",
"readOnly": true
},
"created_at": {
"type": "string",
"readOnly": true
},
"updated_at": {
"type": "string",
"readOnly": true
},
"email_verified": {
"type": "boolean",
"readOnly": true
}
}
}
]
}
},
"security": [
{
"xcAuth": []
}
],
"externalDocs": {
"description": "Find out more about NocoDB",
"url": "http://nocodb.com"
},
"securityDefinitions": {
"xcAuth": {
"type": "apiKey",
"in": "header",
"name": "xc-auth"
}
}
}

48
packages/nocodb/src/lib/noco/meta/api/userApi/ui/auth/swagger.ts

File diff suppressed because one or more lines are too long

40
packages/nocodb/src/lib/noco/meta/api/userApi/userApis.ts

@ -269,8 +269,8 @@ async function passwordForgot(req: Request<any, any>, res): Promise<any> {
}
const email = _email.toLowerCase();
const user = await User.getByEmail(email);
if (user) {
const token = uuidv4();
await User.update(user.id, {
@ -286,15 +286,16 @@ async function passwordForgot(req: Request<any, any>, res): Promise<any> {
subject: 'Password Reset Link',
text: `Visit following link to update your password : ${
(req as any).ncSiteUrl
}/password/reset/${token}.`,
}/api/v1/db/auth/password/reset/${token}.`,
html: ejs.render(template, {
resetLink: (req as any).ncSiteUrl + `/password/reset/${token}`
resetLink: (req as any).ncSiteUrl + `/api/v1/db/auth/password/reset/${token}`
})
})
);
} catch (e) {
console.log(
'Warning : `mailSend` failed, Please configure emailClient configuration.'
console.log(e)
return NcError.badRequest(
'Email Plugin is not found. Please contact administrators to configure it in App Store first.'
);
}
@ -305,12 +306,16 @@ async function passwordForgot(req: Request<any, any>, res): Promise<any> {
description: `requested for password reset `,
ip: (req as any).clientIp
});
} else {
return NcError.badRequest(
'Your email has not been registered.'
);
}
res.json({ msg: 'Check your email if you are registered with us.' });
res.json({ msg: 'Please check your email to reset the password' });
}
async function tokenValidate(req, res): Promise<any> {
const token = req.params.token;
const token = req.params.tokenId;
const user = await Noco.ncMeta.metaGet(null, null, MetaTable.USERS, {
reset_password_token: token
@ -326,7 +331,7 @@ async function tokenValidate(req, res): Promise<any> {
}
async function passwordReset(req, res): Promise<any> {
const token = req.params.token;
const token = req.params.tokenId;
const user = await Noco.ncMeta.metaGet(null, null, MetaTable.USERS, {
reset_password_token: token
@ -364,7 +369,7 @@ async function passwordReset(req, res): Promise<any> {
}
async function emailVerification(req, res): Promise<any> {
const token = req.params.token;
const token = req.params.tokenId;
const user = await Noco.ncMeta.metaGet(null, null, MetaTable.USERS, {
email_verification_token: token
@ -428,6 +433,19 @@ async function refreshToken(req, res): Promise<any> {
}
}
async function renderPasswordReset(req, res): Promise<any> {
try {
res.send(
ejs.render((await import('./ui/auth/resetPassword')).default, {
token: JSON.stringify(req.params.tokenId),
baseUrl: `/`
})
);
} catch (e) {
return res.status(400).json({ msg: e.message });
}
}
const mapRoutes = router => {
// todo: old api - /auth/signup?tool=1
router.post('/auth/user/signup', catchError(signup));
@ -472,5 +490,9 @@ const mapRoutes = router => {
'/api/v1/db/auth/token/refresh',
ncMetaAclMw(refreshToken, 'refreshToken')
);
router.get(
'/api/v1/db/auth/password/reset/:tokenId',
catchError(renderPasswordReset)
);
};
export { mapRoutes as userApis };

Loading…
Cancel
Save