@skroz/profile-api 1.0.16 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/adapters/TypeOrmProfileAdapter.d.ts +4 -1
  2. package/dist/adapters/TypeOrmProfileAdapter.js +15 -1
  3. package/dist/dto/OauthInput.d.ts +14 -0
  4. package/dist/dto/OauthInput.js +65 -0
  5. package/dist/dto/index.d.ts +1 -0
  6. package/dist/dto/index.js +4 -1
  7. package/dist/entities/TypeOrmBaseUser.d.ts +5 -0
  8. package/dist/entities/TypeOrmBaseUser.js +20 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +2 -0
  11. package/dist/oauth/AppleOauth.d.ts +13 -0
  12. package/dist/oauth/AppleOauth.js +84 -0
  13. package/dist/oauth/GoogleOauth.d.ts +12 -0
  14. package/dist/oauth/GoogleOauth.js +54 -0
  15. package/dist/oauth/MailOauth.d.ts +12 -0
  16. package/dist/oauth/MailOauth.js +53 -0
  17. package/dist/oauth/OAuthProvider.d.ts +9 -0
  18. package/dist/oauth/OAuthProvider.js +2 -0
  19. package/dist/oauth/VKOauth.d.ts +12 -0
  20. package/dist/oauth/VKOauth.js +47 -0
  21. package/dist/oauth/YandexOauth.d.ts +11 -0
  22. package/dist/oauth/YandexOauth.js +56 -0
  23. package/dist/oauth/index.d.ts +6 -0
  24. package/dist/oauth/index.js +22 -0
  25. package/dist/resolvers/ProfileResolver.js +5 -3
  26. package/dist/resolvers/createOauthResolver.d.ts +12 -0
  27. package/dist/resolvers/createOauthResolver.js +146 -0
  28. package/dist/types/index.d.ts +4 -1
  29. package/package.json +3 -2
  30. package/src/adapters/TypeOrmProfileAdapter.ts +13 -2
  31. package/src/dto/OauthInput.ts +37 -0
  32. package/src/dto/index.ts +1 -0
  33. package/src/entities/TypeOrmBaseUser.ts +15 -0
  34. package/src/index.ts +2 -0
  35. package/src/oauth/AppleOauth.ts +83 -0
  36. package/src/oauth/GoogleOauth.ts +47 -0
  37. package/src/oauth/MailOauth.ts +49 -0
  38. package/src/oauth/OAuthProvider.ts +10 -0
  39. package/src/oauth/VKOauth.ts +43 -0
  40. package/src/oauth/YandexOauth.ts +49 -0
  41. package/src/oauth/index.ts +6 -0
  42. package/src/resolvers/ProfileResolver.ts +8 -6
  43. package/src/resolvers/createOauthResolver.ts +136 -0
  44. package/src/types/index.ts +3 -1
@@ -0,0 +1,136 @@
1
+ import crypto from 'crypto';
2
+ import { nanoid } from 'nanoid';
3
+ import { Arg, Ctx, Mutation, Resolver } from 'type-graphql';
4
+ import { ProfileAuthService } from '../services/ProfileAuthService';
5
+ import { OAuthProvider } from '../oauth/OAuthProvider';
6
+ import { OauthLoginInput, TelegramAuthData } from '../dto/OauthInput';
7
+ import { ProfileContext } from '../types';
8
+
9
+ export interface OauthResolverDependencies<
10
+ TContext extends ProfileContext = ProfileContext
11
+ > {
12
+ authService: ProfileAuthService | ((ctx: TContext) => ProfileAuthService);
13
+ userType: any;
14
+ providers: Record<string, OAuthProvider>;
15
+ telegramBotToken?: string;
16
+ onUserCreated?: (user: any, ctx: TContext) => Promise<void>;
17
+ onLogin?: (user: any, ctx: TContext) => Promise<void>;
18
+ }
19
+
20
+ function validateTelegramHash(data: TelegramAuthData, botToken: string): boolean {
21
+ const { hash, ...rest } = data;
22
+
23
+ const sorted = Object.keys(rest)
24
+ .filter((k) => {
25
+ const value = (rest as any)[k];
26
+ return value !== undefined && value !== null && value !== '';
27
+ })
28
+ .sort()
29
+ .map((k) => `${k}=${(rest as any)[k]}`)
30
+ .join('\n');
31
+
32
+ const secret = crypto.createHash('sha256').update(botToken).digest();
33
+ const hmac = crypto.createHmac('sha256', secret as unknown as crypto.BinaryLike).update(sorted).digest('hex');
34
+
35
+ return hmac === hash;
36
+ }
37
+
38
+ export function createOauthResolver<
39
+ TContext extends ProfileContext = ProfileContext
40
+ >(deps: OauthResolverDependencies<TContext>): any {
41
+ const {
42
+ authService,
43
+ userType,
44
+ providers,
45
+ telegramBotToken,
46
+ onUserCreated,
47
+ onLogin,
48
+ } = deps;
49
+
50
+ const getAuthService = (ctx: TContext): ProfileAuthService => {
51
+ if (typeof authService === 'function') return authService(ctx);
52
+ return authService;
53
+ };
54
+
55
+ @Resolver(() => userType)
56
+ class OauthResolver {
57
+ @Mutation(() => userType)
58
+ async oauthLogin(
59
+ @Arg('input') input: OauthLoginInput,
60
+ @Ctx() ctx: TContext
61
+ ) {
62
+ const service = getAuthService(ctx);
63
+
64
+ let user: any;
65
+ let isNew = false;
66
+
67
+ if (input.provider === 'telegram') {
68
+ if (!input.tgData) throw new Error('tgData is required for Telegram login');
69
+ if (!telegramBotToken) throw new Error('Telegram bot token is not configured');
70
+
71
+ if (!validateTelegramHash(input.tgData, telegramBotToken)) {
72
+ throw new Error('Telegram auth verification failed');
73
+ }
74
+
75
+ user = await service.db.findUserByTelegramId(input.tgData.id);
76
+
77
+ if (!user) {
78
+ const passwordHash = await service.hashPassword(nanoid(20));
79
+ user = await service.db.createUser({
80
+ email: null,
81
+ passwordHash,
82
+ isTempPassword: true,
83
+ });
84
+ await service.db.updateUserProviderId(user.id, 'telegram', input.tgData.id);
85
+ isNew = true;
86
+ }
87
+ } else {
88
+ if (!input.code) throw new Error('code is required for OAuth login');
89
+
90
+ const provider = providers[input.provider];
91
+ if (!provider) throw new Error(`Unknown OAuth provider: ${input.provider}`);
92
+
93
+ const profile = await provider.exchangeCode(input.code);
94
+
95
+ // 1. Find by provider ID
96
+ user = await service.db.findUserByProviderId(input.provider, profile.providerId);
97
+
98
+ if (!user && profile.email) {
99
+ // 2. Find by email — link provider ID to existing account
100
+ user = await service.db.findUserByEmail(profile.email);
101
+ if (user) {
102
+ await service.db.updateUserProviderId(user.id, input.provider, profile.providerId);
103
+ }
104
+ }
105
+
106
+ if (!user) {
107
+ // 3. Create new user
108
+ const passwordHash = await service.hashPassword(nanoid(20));
109
+ user = await service.db.createUser({
110
+ email: profile.email,
111
+ passwordHash,
112
+ isTempPassword: true,
113
+ });
114
+ await service.db.updateUserProviderId(user.id, input.provider, profile.providerId);
115
+ isNew = true;
116
+ }
117
+ }
118
+
119
+ if (user.isBanned) throw new Error('User is banned');
120
+
121
+ const userAgent = decodeURI(ctx.req.get('user-agent') || '');
122
+ await ctx.req.session.create({
123
+ userId: user.id,
124
+ ip: ctx.req.ip,
125
+ userAgent: userAgent.slice(0, 500),
126
+ });
127
+
128
+ if (isNew && onUserCreated) await onUserCreated(user, ctx);
129
+ if (!isNew && onLogin) await onLogin(user, ctx);
130
+
131
+ return user;
132
+ }
133
+ }
134
+
135
+ return OauthResolver;
136
+ }
@@ -19,8 +19,10 @@ export interface ProfileDbAdapter {
19
19
  findUserByEmail(email: string): Promise<AuthUser | null>;
20
20
  findUserById(id: number): Promise<AuthUser | null>;
21
21
  findUserByTelegramId(telegramId: string): Promise<AuthUser | null>;
22
- createUser(data: { email: string; passwordHash: string }): Promise<AuthUser>;
22
+ createUser(data: { email?: string | null; passwordHash: string; isTempPassword?: boolean }): Promise<AuthUser>;
23
23
  isEmailTaken(email: string, excludeUserId?: number): Promise<boolean>;
24
+ findUserByProviderId(provider: string, id: string): Promise<AuthUser | null>;
25
+ updateUserProviderId(userId: number, provider: string, id: string): Promise<void>;
24
26
  }
25
27
 
26
28
  export interface ProfileAuthConfig {