@skroz/profile-api 1.0.18 → 1.0.20
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.
- package/dist/oauth/AppleOauth.d.ts +1 -0
- package/dist/oauth/AppleOauth.js +3 -0
- package/dist/oauth/GoogleOauth.d.ts +1 -0
- package/dist/oauth/GoogleOauth.js +1 -0
- package/dist/oauth/MailOauth.d.ts +1 -0
- package/dist/oauth/MailOauth.js +1 -0
- package/dist/oauth/OAuthProvider.d.ts +8 -0
- package/dist/oauth/VKOauth.d.ts +1 -0
- package/dist/oauth/VKOauth.js +3 -0
- package/dist/oauth/YandexOauth.d.ts +1 -0
- package/dist/oauth/YandexOauth.js +1 -0
- package/dist/resolvers/createOauthResolver.d.ts +10 -0
- package/dist/resolvers/createOauthResolver.js +95 -1
- package/package.json +2 -2
- package/src/oauth/AppleOauth.ts +4 -0
- package/src/oauth/GoogleOauth.ts +2 -0
- package/src/oauth/MailOauth.ts +2 -0
- package/src/oauth/OAuthProvider.ts +8 -0
- package/src/oauth/VKOauth.ts +4 -0
- package/src/oauth/YandexOauth.ts +2 -0
- package/src/resolvers/createOauthResolver.ts +95 -1
package/dist/oauth/AppleOauth.js
CHANGED
|
@@ -47,6 +47,9 @@ function decodeJwtPayload(token) {
|
|
|
47
47
|
class AppleOauth {
|
|
48
48
|
constructor(config) {
|
|
49
49
|
this.config = config;
|
|
50
|
+
// Apple provides email only on first authorization. On subsequent logins email will be null,
|
|
51
|
+
// so confirmation flags are only set when email is actually present in the profile.
|
|
52
|
+
this.trustedEmail = true;
|
|
50
53
|
}
|
|
51
54
|
exchangeCode(code) {
|
|
52
55
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -17,6 +17,7 @@ const isomorphic_unfetch_1 = __importDefault(require("isomorphic-unfetch"));
|
|
|
17
17
|
class GoogleOauth {
|
|
18
18
|
constructor(config) {
|
|
19
19
|
this.config = config;
|
|
20
|
+
this.trustedEmail = true;
|
|
20
21
|
}
|
|
21
22
|
exchangeCode(code) {
|
|
22
23
|
return __awaiter(this, void 0, void 0, function* () {
|
package/dist/oauth/MailOauth.js
CHANGED
|
@@ -6,4 +6,12 @@ export interface OAuthProfile {
|
|
|
6
6
|
}
|
|
7
7
|
export interface OAuthProvider {
|
|
8
8
|
exchangeCode(code: string): Promise<OAuthProfile>;
|
|
9
|
+
/**
|
|
10
|
+
* Whether this provider verifies the user's email address.
|
|
11
|
+
* When true, isEmailConfirmed and isEmailNotificationEnabled are automatically
|
|
12
|
+
* set to true upon login/registration via this provider.
|
|
13
|
+
*
|
|
14
|
+
* Set to false for providers that do not supply a verified email (e.g. Telegram).
|
|
15
|
+
*/
|
|
16
|
+
readonly trustedEmail: boolean;
|
|
9
17
|
}
|
package/dist/oauth/VKOauth.d.ts
CHANGED
package/dist/oauth/VKOauth.js
CHANGED
|
@@ -17,6 +17,9 @@ const isomorphic_unfetch_1 = __importDefault(require("isomorphic-unfetch"));
|
|
|
17
17
|
class VKOauth {
|
|
18
18
|
constructor(config) {
|
|
19
19
|
this.config = config;
|
|
20
|
+
// VK returns email only if the user has a confirmed email in their account and grants access.
|
|
21
|
+
// If email is absent, trustedEmail guard in the resolver prevents setting confirmation flags.
|
|
22
|
+
this.trustedEmail = true;
|
|
20
23
|
}
|
|
21
24
|
exchangeCode(code) {
|
|
22
25
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -17,6 +17,7 @@ const isomorphic_unfetch_1 = __importDefault(require("isomorphic-unfetch"));
|
|
|
17
17
|
class YandexOauth {
|
|
18
18
|
constructor(config) {
|
|
19
19
|
this.config = config;
|
|
20
|
+
this.trustedEmail = true;
|
|
20
21
|
}
|
|
21
22
|
exchangeCode(code) {
|
|
22
23
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { ProfileAuthService } from '../services/ProfileAuthService';
|
|
2
2
|
import { OAuthProvider } from '../oauth/OAuthProvider';
|
|
3
3
|
import { ProfileContext } from '../types';
|
|
4
|
+
type RedisClient = {
|
|
5
|
+
get(key: string): Promise<string | null>;
|
|
6
|
+
setex(key: string, ttl: number, value: string): Promise<any>;
|
|
7
|
+
del(key: string): Promise<any>;
|
|
8
|
+
};
|
|
4
9
|
export interface OauthResolverDependencies<TContext extends ProfileContext = ProfileContext> {
|
|
5
10
|
authService: ProfileAuthService | ((ctx: TContext) => ProfileAuthService);
|
|
6
11
|
userType: any;
|
|
7
12
|
providers: Record<string, OAuthProvider>;
|
|
8
13
|
telegramBotToken?: string;
|
|
14
|
+
/** Имя Telegram-бота (без @) для авторизации через бота (bot deep link) */
|
|
15
|
+
telegramBotName?: string;
|
|
16
|
+
/** Redis-клиент для авторизации через бота */
|
|
17
|
+
redis?: RedisClient;
|
|
9
18
|
onUserCreated?: (user: any, ctx: TContext) => Promise<void>;
|
|
10
19
|
onLogin?: (user: any, ctx: TContext) => Promise<void>;
|
|
11
20
|
}
|
|
12
21
|
export declare function createOauthResolver<TContext extends ProfileContext = ProfileContext>(deps: OauthResolverDependencies<TContext>): any;
|
|
22
|
+
export {};
|
|
@@ -40,6 +40,36 @@ const crypto_1 = __importDefault(require("crypto"));
|
|
|
40
40
|
const nanoid_1 = require("nanoid");
|
|
41
41
|
const type_graphql_1 = require("type-graphql");
|
|
42
42
|
const OauthInput_1 = require("../dto/OauthInput");
|
|
43
|
+
// Префикс Redis-ключей намеренно длинный, чтобы исключить коллизии с любыми
|
|
44
|
+
// другими ключами в том же Redis-инстансе.
|
|
45
|
+
const TELEGRAM_BOT_AUTH_REDIS_PREFIX = 'skroz:profile:tgbotauth';
|
|
46
|
+
const TELEGRAM_BOT_AUTH_TOKEN_TTL_SECONDS = 300; // 5 минут
|
|
47
|
+
let TelegramBotAuthLinkPayload = class TelegramBotAuthLinkPayload {
|
|
48
|
+
};
|
|
49
|
+
__decorate([
|
|
50
|
+
(0, type_graphql_1.Field)(),
|
|
51
|
+
__metadata("design:type", String)
|
|
52
|
+
], TelegramBotAuthLinkPayload.prototype, "token", void 0);
|
|
53
|
+
__decorate([
|
|
54
|
+
(0, type_graphql_1.Field)(),
|
|
55
|
+
__metadata("design:type", String)
|
|
56
|
+
], TelegramBotAuthLinkPayload.prototype, "url", void 0);
|
|
57
|
+
TelegramBotAuthLinkPayload = __decorate([
|
|
58
|
+
(0, type_graphql_1.ObjectType)()
|
|
59
|
+
], TelegramBotAuthLinkPayload);
|
|
60
|
+
let TelegramBotAuthConfirmResult = class TelegramBotAuthConfirmResult {
|
|
61
|
+
};
|
|
62
|
+
__decorate([
|
|
63
|
+
(0, type_graphql_1.Field)(),
|
|
64
|
+
__metadata("design:type", Boolean)
|
|
65
|
+
], TelegramBotAuthConfirmResult.prototype, "confirmed", void 0);
|
|
66
|
+
__decorate([
|
|
67
|
+
(0, type_graphql_1.Field)(),
|
|
68
|
+
__metadata("design:type", Boolean)
|
|
69
|
+
], TelegramBotAuthConfirmResult.prototype, "expired", void 0);
|
|
70
|
+
TelegramBotAuthConfirmResult = __decorate([
|
|
71
|
+
(0, type_graphql_1.ObjectType)()
|
|
72
|
+
], TelegramBotAuthConfirmResult);
|
|
43
73
|
function validateTelegramHash(data, botToken) {
|
|
44
74
|
const { hash } = data, rest = __rest(data, ["hash"]);
|
|
45
75
|
const sorted = Object.keys(rest)
|
|
@@ -55,7 +85,7 @@ function validateTelegramHash(data, botToken) {
|
|
|
55
85
|
return hmac === hash;
|
|
56
86
|
}
|
|
57
87
|
function createOauthResolver(deps) {
|
|
58
|
-
const { authService, userType, providers, telegramBotToken, onUserCreated, onLogin, } = deps;
|
|
88
|
+
const { authService, userType, providers, telegramBotToken, telegramBotName, redis, onUserCreated, onLogin, } = deps;
|
|
59
89
|
const getAuthService = (ctx) => {
|
|
60
90
|
if (typeof authService === 'function')
|
|
61
91
|
return authService(ctx);
|
|
@@ -114,6 +144,12 @@ function createOauthResolver(deps) {
|
|
|
114
144
|
yield service.db.updateUserProviderId(user.id, input.provider, profile.providerId);
|
|
115
145
|
isNew = true;
|
|
116
146
|
}
|
|
147
|
+
// Auto-confirm email for trusted providers when email is present
|
|
148
|
+
if (provider.trustedEmail && profile.email && (!user.isEmailConfirmed || !user.isEmailNotificationEnabled)) {
|
|
149
|
+
user.isEmailConfirmed = true;
|
|
150
|
+
user.isEmailNotificationEnabled = true;
|
|
151
|
+
yield user.save();
|
|
152
|
+
}
|
|
117
153
|
}
|
|
118
154
|
if (user.isBanned)
|
|
119
155
|
throw new Error('User is banned');
|
|
@@ -130,6 +166,50 @@ function createOauthResolver(deps) {
|
|
|
130
166
|
return user;
|
|
131
167
|
});
|
|
132
168
|
}
|
|
169
|
+
generateTelegramAuthToken() {
|
|
170
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
171
|
+
if (!telegramBotName)
|
|
172
|
+
throw new Error('telegramBotName is not configured');
|
|
173
|
+
if (!redis)
|
|
174
|
+
throw new Error('redis is not configured');
|
|
175
|
+
const token = (0, nanoid_1.nanoid)(32);
|
|
176
|
+
const redisKey = `${TELEGRAM_BOT_AUTH_REDIS_PREFIX}:${token}`;
|
|
177
|
+
yield redis.setex(redisKey, TELEGRAM_BOT_AUTH_TOKEN_TTL_SECONDS, 'pending');
|
|
178
|
+
const url = `https://t.me/${telegramBotName}?start=tgauth_${token}`;
|
|
179
|
+
return { token, url };
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
confirmTelegramAuthToken(token, ctx) {
|
|
183
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
184
|
+
if (!redis)
|
|
185
|
+
throw new Error('redis is not configured');
|
|
186
|
+
const redisKey = `${TELEGRAM_BOT_AUTH_REDIS_PREFIX}:${token}`;
|
|
187
|
+
const val = yield redis.get(redisKey);
|
|
188
|
+
if (!val) {
|
|
189
|
+
return { confirmed: false, expired: true };
|
|
190
|
+
}
|
|
191
|
+
if (val === 'pending') {
|
|
192
|
+
return { confirmed: false, expired: false };
|
|
193
|
+
}
|
|
194
|
+
const userId = parseInt(val, 10);
|
|
195
|
+
if (Number.isNaN(userId)) {
|
|
196
|
+
return { confirmed: false, expired: true };
|
|
197
|
+
}
|
|
198
|
+
const service = getAuthService(ctx);
|
|
199
|
+
const user = yield service.db.findUserById(userId);
|
|
200
|
+
if (!user) {
|
|
201
|
+
return { confirmed: false, expired: true };
|
|
202
|
+
}
|
|
203
|
+
const userAgent = decodeURI(ctx.req.get('user-agent') || '');
|
|
204
|
+
yield ctx.req.session.create({
|
|
205
|
+
userId: user.id,
|
|
206
|
+
ip: ctx.req.ip,
|
|
207
|
+
userAgent: userAgent.slice(0, 500),
|
|
208
|
+
});
|
|
209
|
+
yield redis.del(redisKey);
|
|
210
|
+
return { confirmed: true, expired: false };
|
|
211
|
+
});
|
|
212
|
+
}
|
|
133
213
|
};
|
|
134
214
|
__decorate([
|
|
135
215
|
(0, type_graphql_1.Mutation)(() => userType),
|
|
@@ -139,6 +219,20 @@ function createOauthResolver(deps) {
|
|
|
139
219
|
__metadata("design:paramtypes", [OauthInput_1.OauthLoginInput, Object]),
|
|
140
220
|
__metadata("design:returntype", Promise)
|
|
141
221
|
], OauthResolver.prototype, "oauthLogin", null);
|
|
222
|
+
__decorate([
|
|
223
|
+
(0, type_graphql_1.Mutation)(() => TelegramBotAuthLinkPayload),
|
|
224
|
+
__metadata("design:type", Function),
|
|
225
|
+
__metadata("design:paramtypes", []),
|
|
226
|
+
__metadata("design:returntype", Promise)
|
|
227
|
+
], OauthResolver.prototype, "generateTelegramAuthToken", null);
|
|
228
|
+
__decorate([
|
|
229
|
+
(0, type_graphql_1.Mutation)(() => TelegramBotAuthConfirmResult),
|
|
230
|
+
__param(0, (0, type_graphql_1.Arg)('token')),
|
|
231
|
+
__param(1, (0, type_graphql_1.Ctx)()),
|
|
232
|
+
__metadata("design:type", Function),
|
|
233
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
234
|
+
__metadata("design:returntype", Promise)
|
|
235
|
+
], OauthResolver.prototype, "confirmTelegramAuthToken", null);
|
|
142
236
|
OauthResolver = __decorate([
|
|
143
237
|
(0, type_graphql_1.Resolver)(() => userType)
|
|
144
238
|
], OauthResolver);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skroz/profile-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.20",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": "git@gitlab.com:skroz/libs/utils.git",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"type-graphql": "^1.1.1",
|
|
45
45
|
"typeorm": "^0.2.45"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "7613b73a6980c945cb2ef597747f74855bc2cbfb"
|
|
48
48
|
}
|
package/src/oauth/AppleOauth.ts
CHANGED
|
@@ -43,6 +43,10 @@ function decodeJwtPayload(token: string): Record<string, any> {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export class AppleOauth implements OAuthProvider {
|
|
46
|
+
// Apple provides email only on first authorization. On subsequent logins email will be null,
|
|
47
|
+
// so confirmation flags are only set when email is actually present in the profile.
|
|
48
|
+
readonly trustedEmail = true;
|
|
49
|
+
|
|
46
50
|
constructor(private config: AppleOauthConfig) {}
|
|
47
51
|
|
|
48
52
|
async exchangeCode(code: string): Promise<OAuthProfile> {
|
package/src/oauth/GoogleOauth.ts
CHANGED
package/src/oauth/MailOauth.ts
CHANGED
|
@@ -7,4 +7,12 @@ export interface OAuthProfile {
|
|
|
7
7
|
|
|
8
8
|
export interface OAuthProvider {
|
|
9
9
|
exchangeCode(code: string): Promise<OAuthProfile>;
|
|
10
|
+
/**
|
|
11
|
+
* Whether this provider verifies the user's email address.
|
|
12
|
+
* When true, isEmailConfirmed and isEmailNotificationEnabled are automatically
|
|
13
|
+
* set to true upon login/registration via this provider.
|
|
14
|
+
*
|
|
15
|
+
* Set to false for providers that do not supply a verified email (e.g. Telegram).
|
|
16
|
+
*/
|
|
17
|
+
readonly trustedEmail: boolean;
|
|
10
18
|
}
|
package/src/oauth/VKOauth.ts
CHANGED
|
@@ -8,6 +8,10 @@ interface VKOauthConfig {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export class VKOauth implements OAuthProvider {
|
|
11
|
+
// VK returns email only if the user has a confirmed email in their account and grants access.
|
|
12
|
+
// If email is absent, trustedEmail guard in the resolver prevents setting confirmation flags.
|
|
13
|
+
readonly trustedEmail = true;
|
|
14
|
+
|
|
11
15
|
constructor(private config: VKOauthConfig) {}
|
|
12
16
|
|
|
13
17
|
async exchangeCode(code: string): Promise<OAuthProfile> {
|
package/src/oauth/YandexOauth.ts
CHANGED
|
@@ -1,11 +1,40 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import { nanoid } from 'nanoid';
|
|
3
|
-
import { Arg, Ctx, Mutation, Resolver } from 'type-graphql';
|
|
3
|
+
import { Arg, Ctx, Mutation, ObjectType, Field, Resolver } from 'type-graphql';
|
|
4
4
|
import { ProfileAuthService } from '../services/ProfileAuthService';
|
|
5
5
|
import { OAuthProvider } from '../oauth/OAuthProvider';
|
|
6
6
|
import { OauthLoginInput, TelegramAuthData } from '../dto/OauthInput';
|
|
7
7
|
import { ProfileContext } from '../types';
|
|
8
8
|
|
|
9
|
+
// Префикс Redis-ключей намеренно длинный, чтобы исключить коллизии с любыми
|
|
10
|
+
// другими ключами в том же Redis-инстансе.
|
|
11
|
+
const TELEGRAM_BOT_AUTH_REDIS_PREFIX = 'skroz:profile:tgbotauth';
|
|
12
|
+
const TELEGRAM_BOT_AUTH_TOKEN_TTL_SECONDS = 300; // 5 минут
|
|
13
|
+
|
|
14
|
+
@ObjectType()
|
|
15
|
+
class TelegramBotAuthLinkPayload {
|
|
16
|
+
@Field()
|
|
17
|
+
token!: string;
|
|
18
|
+
|
|
19
|
+
@Field()
|
|
20
|
+
url!: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@ObjectType()
|
|
24
|
+
class TelegramBotAuthConfirmResult {
|
|
25
|
+
@Field()
|
|
26
|
+
confirmed!: boolean;
|
|
27
|
+
|
|
28
|
+
@Field()
|
|
29
|
+
expired!: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type RedisClient = {
|
|
33
|
+
get(key: string): Promise<string | null>;
|
|
34
|
+
setex(key: string, ttl: number, value: string): Promise<any>;
|
|
35
|
+
del(key: string): Promise<any>;
|
|
36
|
+
};
|
|
37
|
+
|
|
9
38
|
export interface OauthResolverDependencies<
|
|
10
39
|
TContext extends ProfileContext = ProfileContext
|
|
11
40
|
> {
|
|
@@ -13,6 +42,10 @@ export interface OauthResolverDependencies<
|
|
|
13
42
|
userType: any;
|
|
14
43
|
providers: Record<string, OAuthProvider>;
|
|
15
44
|
telegramBotToken?: string;
|
|
45
|
+
/** Имя Telegram-бота (без @) для авторизации через бота (bot deep link) */
|
|
46
|
+
telegramBotName?: string;
|
|
47
|
+
/** Redis-клиент для авторизации через бота */
|
|
48
|
+
redis?: RedisClient;
|
|
16
49
|
onUserCreated?: (user: any, ctx: TContext) => Promise<void>;
|
|
17
50
|
onLogin?: (user: any, ctx: TContext) => Promise<void>;
|
|
18
51
|
}
|
|
@@ -43,6 +76,8 @@ export function createOauthResolver<
|
|
|
43
76
|
userType,
|
|
44
77
|
providers,
|
|
45
78
|
telegramBotToken,
|
|
79
|
+
telegramBotName,
|
|
80
|
+
redis,
|
|
46
81
|
onUserCreated,
|
|
47
82
|
onLogin,
|
|
48
83
|
} = deps;
|
|
@@ -114,6 +149,13 @@ export function createOauthResolver<
|
|
|
114
149
|
await service.db.updateUserProviderId(user.id, input.provider, profile.providerId);
|
|
115
150
|
isNew = true;
|
|
116
151
|
}
|
|
152
|
+
|
|
153
|
+
// Auto-confirm email for trusted providers when email is present
|
|
154
|
+
if (provider.trustedEmail && profile.email && (!user.isEmailConfirmed || !user.isEmailNotificationEnabled)) {
|
|
155
|
+
user.isEmailConfirmed = true;
|
|
156
|
+
user.isEmailNotificationEnabled = true;
|
|
157
|
+
await user.save();
|
|
158
|
+
}
|
|
117
159
|
}
|
|
118
160
|
|
|
119
161
|
if (user.isBanned) throw new Error('User is banned');
|
|
@@ -130,6 +172,58 @@ export function createOauthResolver<
|
|
|
130
172
|
|
|
131
173
|
return user;
|
|
132
174
|
}
|
|
175
|
+
|
|
176
|
+
@Mutation(() => TelegramBotAuthLinkPayload)
|
|
177
|
+
async generateTelegramAuthToken(): Promise<TelegramBotAuthLinkPayload> {
|
|
178
|
+
if (!telegramBotName) throw new Error('telegramBotName is not configured');
|
|
179
|
+
if (!redis) throw new Error('redis is not configured');
|
|
180
|
+
|
|
181
|
+
const token = nanoid(32);
|
|
182
|
+
const redisKey = `${TELEGRAM_BOT_AUTH_REDIS_PREFIX}:${token}`;
|
|
183
|
+
await redis.setex(redisKey, TELEGRAM_BOT_AUTH_TOKEN_TTL_SECONDS, 'pending');
|
|
184
|
+
const url = `https://t.me/${telegramBotName}?start=tgauth_${token}`;
|
|
185
|
+
return { token, url };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@Mutation(() => TelegramBotAuthConfirmResult)
|
|
189
|
+
async confirmTelegramAuthToken(
|
|
190
|
+
@Arg('token') token: string,
|
|
191
|
+
@Ctx() ctx: TContext
|
|
192
|
+
): Promise<TelegramBotAuthConfirmResult> {
|
|
193
|
+
if (!redis) throw new Error('redis is not configured');
|
|
194
|
+
|
|
195
|
+
const redisKey = `${TELEGRAM_BOT_AUTH_REDIS_PREFIX}:${token}`;
|
|
196
|
+
const val = await redis.get(redisKey);
|
|
197
|
+
|
|
198
|
+
if (!val) {
|
|
199
|
+
return { confirmed: false, expired: true };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (val === 'pending') {
|
|
203
|
+
return { confirmed: false, expired: false };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const userId = parseInt(val, 10);
|
|
207
|
+
if (Number.isNaN(userId)) {
|
|
208
|
+
return { confirmed: false, expired: true };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const service = getAuthService(ctx);
|
|
212
|
+
const user = await service.db.findUserById(userId);
|
|
213
|
+
if (!user) {
|
|
214
|
+
return { confirmed: false, expired: true };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const userAgent = decodeURI(ctx.req.get('user-agent') || '');
|
|
218
|
+
await ctx.req.session.create({
|
|
219
|
+
userId: user.id,
|
|
220
|
+
ip: ctx.req.ip,
|
|
221
|
+
userAgent: userAgent.slice(0, 500),
|
|
222
|
+
});
|
|
223
|
+
await redis.del(redisKey);
|
|
224
|
+
|
|
225
|
+
return { confirmed: true, expired: false };
|
|
226
|
+
}
|
|
133
227
|
}
|
|
134
228
|
|
|
135
229
|
return OauthResolver;
|