Skip to content

Commit a52ce97

Browse files
committed
feat(#1908): inproved generate matrix access token
1 parent b2278ba commit a52ce97

6 files changed

Lines changed: 236 additions & 7 deletions

File tree

apps/web/src/app/api/matrix/token/route.ts

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ import {
22
createMatrixUserLinkAction,
33
decryptMatrixToken,
44
determineEnvironment,
5+
Environment,
56
getDecoratedPrivyId,
67
getLinkByPrivyUserId,
78
MatrixSharedSecret,
9+
MatrixUserLink,
10+
updateEncryptedAccessTokenAction,
11+
updateMatrixUserLink,
812
} from '@hypha-platform/core/server';
913
import { PrivyClient } from '@privy-io/server-auth';
1014
import { NextRequest, NextResponse } from 'next/server';
@@ -14,6 +18,7 @@ const PRIVY_APP_SECRET = process.env.PRIVY_APP_SECRET ?? '';
1418
const MATRIX_HOMESERVER_URL =
1519
process.env.NEXT_PUBLIC_MATRIX_HOMESERVER_URL ?? '';
1620
const DEFAULT_ROOM_ID = process.env.DEFAULT_ROOM_ID ?? '';
21+
const ADMIN_SUFFIX = 'hypha_admin';
1722

1823
export async function GET(request: NextRequest) {
1924
const authHeader = request.headers.get('Authorization');
@@ -31,6 +36,35 @@ export async function GET(request: NextRequest) {
3136
const privy = new PrivyClient(PRIVY_APP_ID, PRIVY_APP_SECRET);
3237
const matrixAuthClient = new MatrixSharedSecret();
3338

39+
const getAdminRecord = async (
40+
adminUsername: string,
41+
environment: Environment,
42+
authToken: string,
43+
) => {
44+
const record = await getLinkByPrivyUserId({
45+
privyUserId: adminUsername,
46+
environment,
47+
});
48+
if (record) {
49+
return record;
50+
}
51+
const {
52+
accessToken: encryptedAccessToken,
53+
deviceId,
54+
userId: matrixUserId,
55+
} = await matrixAuthClient.registerUser(adminUsername, true);
56+
return (await createMatrixUserLinkAction(
57+
{
58+
environment,
59+
encryptedAccessToken,
60+
deviceId,
61+
matrixUserId,
62+
privyUserId: adminUsername,
63+
},
64+
{ authToken },
65+
)) as MatrixUserLink;
66+
};
67+
3468
try {
3569
const authToken = authHeader.replace('Bearer ', '');
3670
const { userId: privyUserId } = await privy.verifyAuthToken(authToken);
@@ -51,7 +85,63 @@ export async function GET(request: NextRequest) {
5185
},
5286
});
5387
} else {
54-
//TODO: update access token
88+
const adminMatrixUsername = getDecoratedPrivyId(
89+
ADMIN_SUFFIX,
90+
environment,
91+
);
92+
const admin = await getAdminRecord(
93+
adminMatrixUsername,
94+
environment,
95+
authToken,
96+
);
97+
if (admin) {
98+
const adminAccessToken = decryptMatrixToken(
99+
admin.encryptedAccessToken,
100+
);
101+
const { ok, password } = await matrixAuthClient.resetPassword(
102+
existing.matrixUserId,
103+
adminAccessToken,
104+
);
105+
if (ok) {
106+
const {
107+
accessToken: encryptedAccessToken,
108+
deviceId,
109+
userId: matrixUserId,
110+
} = await matrixAuthClient.loginUser(
111+
existing.matrixUserId,
112+
password,
113+
);
114+
115+
await updateEncryptedAccessTokenAction(
116+
{
117+
privyUserId,
118+
environment,
119+
encryptedAccessToken,
120+
},
121+
{ authToken },
122+
);
123+
124+
return NextResponse.json({
125+
accessToken: decryptMatrixToken(encryptedAccessToken),
126+
userId: matrixUserId,
127+
homeserverUrl: MATRIX_HOMESERVER_URL,
128+
deviceId,
129+
elementConfig: {
130+
// defaultRoomId: DEFAULT_ROOM_ID,
131+
theme: 'dark',
132+
},
133+
});
134+
}
135+
136+
return NextResponse.json(
137+
{
138+
error: 'Token generation failed',
139+
},
140+
{
141+
status: 500,
142+
},
143+
);
144+
}
55145
}
56146
}
57147

@@ -62,6 +152,58 @@ export async function GET(request: NextRequest) {
62152
userId: matrixUserId,
63153
} = await matrixAuthClient.registerUser(matrixUsername);
64154

155+
if (!encryptedAccessToken) {
156+
const adminMatrixUsername = getDecoratedPrivyId(
157+
ADMIN_SUFFIX,
158+
environment,
159+
);
160+
const admin = await getAdminRecord(
161+
adminMatrixUsername,
162+
environment,
163+
authToken,
164+
);
165+
const adminAccessToken = decryptMatrixToken(admin.encryptedAccessToken);
166+
const { ok, password } = await matrixAuthClient.resetPassword(
167+
matrixUserId,
168+
adminAccessToken,
169+
);
170+
if (ok) {
171+
const {
172+
accessToken: encryptedAccessToken,
173+
deviceId,
174+
userId,
175+
} = await matrixAuthClient.loginUser(matrixUserId, password);
176+
177+
await updateEncryptedAccessTokenAction(
178+
{
179+
privyUserId,
180+
environment,
181+
encryptedAccessToken,
182+
},
183+
{ authToken },
184+
);
185+
186+
return NextResponse.json({
187+
accessToken: decryptMatrixToken(encryptedAccessToken),
188+
userId,
189+
homeserverUrl: MATRIX_HOMESERVER_URL,
190+
deviceId,
191+
elementConfig: {
192+
// defaultRoomId: DEFAULT_ROOM_ID,
193+
theme: 'dark',
194+
},
195+
});
196+
}
197+
return NextResponse.json(
198+
{
199+
error: 'Token generation failed',
200+
},
201+
{
202+
status: 500,
203+
},
204+
);
205+
}
206+
65207
await createMatrixUserLinkAction(
66208
{
67209
environment,

packages/core/src/coherence/lib/matrix-shared-secret.ts

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import crypto from 'node:crypto';
2-
import { encryptMatrixToken, hashHmacSha1Hex } from '../../server';
2+
import { hashHmacSha1Hex } from '../../common/server/encrypt-aes';
3+
import { encryptMatrixToken } from '../../common/server/encrypt-matrix-token';
34

45
type VersionsResponse = {
56
versions: Array<string>;
@@ -67,11 +68,10 @@ export class MatrixSharedSecret {
6768
return available.available;
6869
}
6970

70-
async registerUserNonce(username: string) {
71+
async registerUserNonce(username: string, isAdmin: boolean = false) {
7172
const nonce = await this.getNonce();
7273
const endpoint = '/_synapse/admin/v1/register';
7374
const password = crypto.randomBytes(32).toString('hex');
74-
const isAdmin = false;
7575
const admin = isAdmin ? 'admin' : 'notadmin';
7676
const text = `${nonce}\0${username}\0${password}\0${admin}`;
7777
const mac = hashHmacSha1Hex(text, this.registrationSharedSecret);
@@ -96,10 +96,25 @@ export class MatrixSharedSecret {
9696
return response;
9797
}
9898

99-
async registerUser(username: string): Promise<RegisterResponse> {
100-
const response = await this.registerUserNonce(username);
99+
async registerUser(
100+
username: string,
101+
isAdmin: boolean = false,
102+
): Promise<RegisterResponse> {
103+
const response = await this.registerUserNonce(username, isAdmin);
101104
const data = await response.json();
102105

106+
if (!response.ok) {
107+
console.warn('Cannot register user:', data);
108+
109+
if (data.errcode === 'M_USER_IN_USE') {
110+
return {
111+
accessToken: '',
112+
userId: '',
113+
deviceId: '',
114+
};
115+
}
116+
}
117+
103118
await this.changePassword(
104119
data.access_token,
105120
crypto.randomBytes(32).toString('hex'),
@@ -112,6 +127,29 @@ export class MatrixSharedSecret {
112127
};
113128
}
114129

130+
async resetPassword(username: string, adminAccessToken: string) {
131+
const password = crypto.randomBytes(32).toString('hex');
132+
const response = await fetch(
133+
`${this.matrixHomeserverUrl}/_dendrite/admin/resetPassword/${username}`,
134+
{
135+
method: 'POST',
136+
headers: {
137+
'Content-Type': 'application/json',
138+
Authorization: `Bearer ${adminAccessToken}`,
139+
},
140+
body: JSON.stringify({
141+
password,
142+
logout_devices: true,
143+
}),
144+
},
145+
);
146+
const data = await response.json();
147+
if (!response.ok) {
148+
console.warn('Cannot reset password', data);
149+
}
150+
return { ok: data.password_updated, password };
151+
}
152+
115153
async removeUser(username: string) {
116154
const response = await fetch(
117155
`${this.matrixHomeserverUrl}/_dendrite/admin/deactivate/${username}`,
@@ -157,6 +195,37 @@ export class MatrixSharedSecret {
157195
}
158196
}
159197

198+
async loginUser(username: string, password: string) {
199+
const version = await this.getEffectiveVersion();
200+
const endpoint = `/_matrix/client/${version}/login`;
201+
202+
const response = await fetch(`${this.matrixHomeserverUrl}${endpoint}`, {
203+
method: 'POST',
204+
headers: {
205+
'Content-Type': 'application/json',
206+
},
207+
body: JSON.stringify({
208+
identifier: {
209+
type: 'm.id.user',
210+
user: username,
211+
},
212+
initial_device_display_name: `device_${Date.now()}`,
213+
password,
214+
type: 'm.login.password',
215+
}),
216+
});
217+
const data = await response.json();
218+
if (!response.ok) {
219+
console.warn('Cannot login user', data);
220+
}
221+
console.log('Login data response:', data);
222+
return {
223+
accessToken: encryptMatrixToken(data.access_token),
224+
userId: data.user_id,
225+
deviceId: data.device_id,
226+
};
227+
}
228+
160229
private async changePassword(accessToken: string, newPassword: string) {
161230
const version = await this.getEffectiveVersion();
162231
const endpoint = `/_matrix/client/${version}/account/password`;
@@ -173,5 +242,6 @@ export class MatrixSharedSecret {
173242
}),
174243
});
175244
const data = await response.json();
245+
return data;
176246
}
177247
}

packages/core/src/matrix/server/actions.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import { db } from '@hypha-platform/storage-postgres';
44
import {
55
CreateMatrixUserLinkInput,
6+
GetMatrixUserLinkActionInput,
67
UpdateEncryptedAccessTokenInput,
78
} from '../types';
89
import { createMatrixUserLink, updateMatrixUserLink } from './mutations';
10+
import { findLinkByPrivyUserId } from './queries';
911

1012
export async function createMatrixUserLinkAction(
1113
data: CreateMatrixUserLinkInput,
@@ -28,3 +30,13 @@ export async function updateEncryptedAccessTokenAction(
2830
}
2931
return await updateMatrixUserLink(data, { db });
3032
}
33+
34+
export async function getMatrixUserLinkAction(
35+
data: GetMatrixUserLinkActionInput,
36+
{ authToken }: { authToken?: string },
37+
) {
38+
if (!authToken) {
39+
throw new Error('authToken is required to get Matrix user link');
40+
}
41+
return await findLinkByPrivyUserId(data, { db });
42+
}

packages/core/src/matrix/server/queries.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { MatrixUserLink } from '@hypha-platform/storage-postgres';
22
import { DbConfig } from '../../server';
3-
import { and } from 'drizzle-orm';
43

54
export const findLinkByPrivyUserId = async (
65
{ privyUserId, environment }: { privyUserId: string; environment: string },

packages/core/src/matrix/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ export interface UpdateEncryptedAccessTokenInput {
2424
environment: string;
2525
}
2626

27+
export interface GetMatrixUserLinkActionInput {
28+
environment: string;
29+
privyUserId: string;
30+
}
31+
2732
export interface Message {
2833
id: string;
2934
sender: string;

packages/epics/src/coherence/components/chat-detail.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const ChatDetail = ({
3030
const { updateCoherenceBySlug } = useCoherenceMutationsWeb2Rsc(authToken);
3131

3232
React.useEffect(() => {
33+
//TODO: improve compute views
3334
if (!conversation) {
3435
return;
3536
}

0 commit comments

Comments
 (0)