-
Notifications
You must be signed in to change notification settings - Fork 13
/
api.ts
149 lines (136 loc) · 4.5 KB
/
api.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import { Response } from "express";
import { PuppetBridge, IAuthedRequest, Log } from "mx-puppet-bridge";
import { IgApiClient, IgCheckpointError, IgLoginTwoFactorRequiredError } from "instagram-private-api";
import { get } from "lodash";
import { fillCookieJar, getSessionCookie } from "./login";
const CREATED = 201;
const ACCEPTED = 202;
const FORBIDDEN = 403;
const log = new Log("InstagramPuppet:api");
export class InstagramProvisioningAPI {
private clients: { [userId: string]: IgApiClient } = {};
private secondFactorState: {
[userId: string]: {
twoFactorIdentifier: string,
username: string,
},
} = {};
constructor(
private puppet: PuppetBridge,
) {
const api = puppet.provisioningAPI;
api.v1.post("/login/cookie", this.loginWithCookie.bind(this));
api.v1.post("/login/password", this.loginWithPassword.bind(this));
api.v1.post("/login/checkpoint", this.loginCheckpoint.bind(this));
api.v1.post("/login/2fa", this.loginSecondFactor.bind(this));
}
private getClient(userId: string): IgApiClient {
let igc = this.clients[userId];
if (!igc) {
igc = this.clients[userId] = new IgApiClient();
}
return igc;
}
private popClient(userId: string): IgApiClient {
const igc = this.clients[userId];
delete this.clients[userId];
return igc;
}
private async loginWithCookie(req: IAuthedRequest, res: Response) {
await fillCookieJar(this.getClient(req.userId), req.body.session_id);
await this.finishLogin(req.userId, res);
}
private async loginWithPassword(req: IAuthedRequest, res: Response) {
const igc = this.getClient(req.userId);
igc.state.generateDevice(req.body.username);
await igc.simulate.preLoginFlow();
try {
const auth = await igc.account.login(req.body.username, req.body.password);
await igc.account.currentUser();
log.verbose(auth);
} catch (err) {
if (err instanceof IgCheckpointError) {
log.verbose("Requesting \"it was me\" button");
log.verbose(igc.state.checkpoint); // Checkpoint info here
await igc.challenge.auto(true);
log.verbose(igc.state.checkpoint); // Challenge info here
res.status(ACCEPTED).json({ next_step: "/login/checkpoint" });
} else if (err instanceof IgLoginTwoFactorRequiredError) {
const twoFactorIdentifier = get(err, "response.body.two_factor_info.two_factor_identifier");
if (!twoFactorIdentifier) {
this.secondFactorState[req.userId] = { twoFactorIdentifier, username: req.body.username };
res.status(ACCEPTED).json({ next_step: "/login/2fa" });
} else {
res.status(FORBIDDEN).json({
errcode: "M_UNKNOWN",
error: "Unable to login, no 2fa identifier found",
});
}
} else {
res.status(FORBIDDEN).json({
errcode: "M_FORBIDDEN",
error: "Invalid username or password",
});
}
return;
}
await this.finishLogin(req.userId, res);
}
private async loginCheckpoint(req: IAuthedRequest, res: Response) {
try {
const ret = await this.getClient(req.userId).challenge.sendSecurityCode(req.body.code);
log.verbose(ret);
} catch (err) {
log.warn(err);
res.status(FORBIDDEN).json({ errcode: "M_FORBIDDEN", error: err.toString() });
return;
}
await this.finishLogin(req.userId, res);
}
private async loginSecondFactor(req: IAuthedRequest, res: Response) {
const igc = this.getClient(req.userId);
if (!this.secondFactorState.hasOwnProperty(req.userId)) {
res.status(FORBIDDEN).json({ errcode: "M_FORBIDDEN", error: "Login not started" });
return;
}
const { username, twoFactorIdentifier } = this.secondFactorState[req.userId];
try {
let ret;
try {
// first try if this is SMS login
ret = await igc.account.twoFactorLogin({
username,
verificationCode: req.body.code,
twoFactorIdentifier,
verificationMethod: "1",
});
} catch (e) {
// then try if this is OTP login
ret = await igc.account.twoFactorLogin({
username,
verificationCode: req.body.code,
twoFactorIdentifier,
verificationMethod: "0",
});
}
log.verbose(ret);
} catch (err) {
log.warn(err);
res.status(FORBIDDEN).json({ errcode: "M_FORBIDDEN", error: err.toString() });
return;
}
await this.finishLogin(req.userId, res);
}
private async finishLogin(userId: string, res: Response) {
const igc = this.popClient(userId);
const rs = await getSessionCookie(igc, {
success: false,
});
if (!rs.success) {
res.status(FORBIDDEN).json(res);
} else {
const puppetId = await this.puppet.provisioner.new(userId, rs.data as any);
res.status(CREATED).json({ puppet_id: puppetId });
}
}
}