mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 20:08:37 +00:00
fix(server): change password with token should be public (#7855)
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
EmailTokenNotFound,
|
||||
EmailVerificationRequired,
|
||||
InvalidEmailToken,
|
||||
LinkExpired,
|
||||
SameEmailProvided,
|
||||
SkipThrottle,
|
||||
Throttle,
|
||||
@@ -89,12 +90,17 @@ export class AuthResolver {
|
||||
};
|
||||
}
|
||||
|
||||
@Mutation(() => UserType)
|
||||
@Public()
|
||||
@Mutation(() => Boolean)
|
||||
async changePassword(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('token') token: string,
|
||||
@Args('newPassword') newPassword: string
|
||||
@Args('newPassword') newPassword: string,
|
||||
@Args('userId', { type: () => String, nullable: true }) userId?: string
|
||||
) {
|
||||
if (!userId) {
|
||||
throw new LinkExpired();
|
||||
}
|
||||
|
||||
const config = await this.config.runtime.fetchAll({
|
||||
'auth/password.max': true,
|
||||
'auth/password.min': true,
|
||||
@@ -108,7 +114,7 @@ export class AuthResolver {
|
||||
TokenType.ChangePassword,
|
||||
token,
|
||||
{
|
||||
credential: user.id,
|
||||
credential: userId,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -116,10 +122,10 @@ export class AuthResolver {
|
||||
throw new InvalidEmailToken();
|
||||
}
|
||||
|
||||
await this.auth.changePassword(user.id, newPassword);
|
||||
await this.auth.revokeUserSessions(user.id);
|
||||
await this.auth.changePassword(userId, newPassword);
|
||||
await this.auth.revokeUserSessions(userId);
|
||||
|
||||
return user;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Mutation(() => UserType)
|
||||
@@ -163,7 +169,7 @@ export class AuthResolver {
|
||||
user.id
|
||||
);
|
||||
|
||||
const url = this.url.link(callbackUrl, { token });
|
||||
const url = this.url.link(callbackUrl, { userId: user.id, token });
|
||||
|
||||
const res = await this.auth.sendChangePasswordEmail(user.email, url);
|
||||
|
||||
@@ -176,19 +182,7 @@ export class AuthResolver {
|
||||
@Args('callbackUrl') callbackUrl: string,
|
||||
@Args('email', { nullable: true }) _email?: string
|
||||
) {
|
||||
if (!user.emailVerified) {
|
||||
throw new EmailVerificationRequired();
|
||||
}
|
||||
|
||||
const token = await this.token.createToken(
|
||||
TokenType.ChangePassword,
|
||||
user.id
|
||||
);
|
||||
|
||||
const url = this.url.link(callbackUrl, { token });
|
||||
|
||||
const res = await this.auth.sendSetPasswordEmail(user.email, url);
|
||||
return !res.rejected.length;
|
||||
return this.sendChangePasswordEmail(user, callbackUrl);
|
||||
}
|
||||
|
||||
// The change email step is:
|
||||
@@ -305,6 +299,7 @@ export class AuthResolver {
|
||||
TokenType.ChangePassword,
|
||||
userId
|
||||
);
|
||||
return this.url.link(callbackUrl, { token });
|
||||
|
||||
return this.url.link(callbackUrl, { userId, token });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,6 +279,10 @@ export const USER_FRIENDLY_ERRORS = {
|
||||
type: 'invalid_input',
|
||||
message: 'An invalid email token provided.',
|
||||
},
|
||||
link_expired: {
|
||||
type: 'bad_request',
|
||||
message: 'The link has expired.',
|
||||
},
|
||||
|
||||
// Authentication & Permission Errors
|
||||
authentication_required: {
|
||||
|
||||
@@ -137,6 +137,12 @@ export class InvalidEmailToken extends UserFriendlyError {
|
||||
}
|
||||
}
|
||||
|
||||
export class LinkExpired extends UserFriendlyError {
|
||||
constructor(message?: string) {
|
||||
super('bad_request', 'link_expired', message);
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthenticationRequired extends UserFriendlyError {
|
||||
constructor(message?: string) {
|
||||
super('authentication_required', 'authentication_required', message);
|
||||
@@ -520,6 +526,7 @@ export enum ErrorNames {
|
||||
SIGN_UP_FORBIDDEN,
|
||||
EMAIL_TOKEN_NOT_FOUND,
|
||||
INVALID_EMAIL_TOKEN,
|
||||
LINK_EXPIRED,
|
||||
AUTHENTICATION_REQUIRED,
|
||||
ACTION_FORBIDDEN,
|
||||
ACCESS_DENIED,
|
||||
|
||||
@@ -235,6 +235,7 @@ enum ErrorNames {
|
||||
INVALID_OAUTH_CALLBACK_STATE
|
||||
INVALID_PASSWORD_LENGTH
|
||||
INVALID_RUNTIME_CONFIG_TYPE
|
||||
LINK_EXPIRED
|
||||
MAILER_SERVICE_IS_NOT_CONFIGURED
|
||||
MEMBER_QUOTA_EXCEEDED
|
||||
MISSING_OAUTH_QUERY_PARAMETER
|
||||
@@ -409,7 +410,7 @@ type Mutation {
|
||||
addWorkspaceFeature(feature: FeatureType!, workspaceId: String!): Int!
|
||||
cancelSubscription(idempotencyKey: String!, plan: SubscriptionPlan = Pro): UserSubscription!
|
||||
changeEmail(email: String!, token: String!): UserType!
|
||||
changePassword(newPassword: String!, token: String!): UserType!
|
||||
changePassword(newPassword: String!, token: String!, userId: String): Boolean!
|
||||
|
||||
"""Cleanup sessions"""
|
||||
cleanupCopilotSession(options: DeleteSessionInput!): [String!]!
|
||||
|
||||
@@ -132,13 +132,14 @@ test('set and change password', async t => {
|
||||
);
|
||||
|
||||
const newPassword = randomBytes(16).toString('hex');
|
||||
const userId = await changePassword(
|
||||
const success = await changePassword(
|
||||
app,
|
||||
u1.token.token,
|
||||
u1.id,
|
||||
setPasswordToken as string,
|
||||
newPassword
|
||||
);
|
||||
t.is(u1.id, userId, 'failed to set password');
|
||||
|
||||
t.true(success, 'failed to change password');
|
||||
|
||||
const ret = auth.signIn(u1Email, newPassword);
|
||||
t.notThrowsAsync(ret, 'failed to check password');
|
||||
@@ -201,7 +202,7 @@ test('should revoke token after change user identify', async t => {
|
||||
await sendSetPasswordEmail(app, u3.token.token, u3Email, 'affine.pro');
|
||||
const token = await getTokenFromLatestMailMessage();
|
||||
const newPassword = randomBytes(16).toString('hex');
|
||||
await changePassword(app, u3.token.token, token as string, newPassword);
|
||||
await changePassword(app, u3.id, token as string, newPassword);
|
||||
|
||||
const user = await currentUser(app, u3.token.token);
|
||||
t.is(user, null, 'token should be revoked');
|
||||
|
||||
@@ -129,26 +129,23 @@ export async function sendSetPasswordEmail(
|
||||
|
||||
export async function changePassword(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
userId: string,
|
||||
token: string,
|
||||
password: string
|
||||
): Promise<string> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(userToken, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation changePassword($token: String!, $password: String!) {
|
||||
changePassword(token: $token, newPassword: $password) {
|
||||
id
|
||||
}
|
||||
mutation changePassword($token: String!, $userId: String!, $password: String!) {
|
||||
changePassword(token: $token, userId: $userId, newPassword: $password)
|
||||
}
|
||||
`,
|
||||
variables: { token, password },
|
||||
variables: { token, password, userId },
|
||||
})
|
||||
.expect(200);
|
||||
return res.body.data.changePassword.id;
|
||||
return res.body.data.changePassword;
|
||||
}
|
||||
|
||||
export async function sendVerifyChangeEmail(
|
||||
|
||||
Reference in New Issue
Block a user