-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
callback.js
157 lines (142 loc) · 4.92 KB
/
callback.js
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
150
151
152
153
154
155
156
157
import { decode as jwtDecode } from "jsonwebtoken"
import oAuthClient from "./client"
import logger from "../../../lib/logger"
import { OAuthCallbackError } from "../../../lib/errors"
/** @param {import("types/internals").NextAuthRequest} req */
export default async function oAuthCallback(req) {
const { provider, pkce } = req.options
const client = oAuthClient(provider)
if (provider.version?.startsWith("2.")) {
// The "user" object is specific to the Apple provider and is provided on first sign in
// e.g. {"name":{"firstName":"Johnny","lastName":"Appleseed"},"email":"johnny.appleseed@nextauth.com"}
let { code, user } = req.query // eslint-disable-line camelcase
if (req.method === "POST") {
try {
const body = JSON.parse(JSON.stringify(req.body))
if (body.error) {
throw new Error(body.error)
}
code = body.code
user = body.user != null ? JSON.parse(body.user) : null
} catch (error) {
logger.error(
"OAUTH_CALLBACK_HANDLER_ERROR",
error,
req.body,
provider.id,
code
)
throw error
}
}
// REVIEW: Is this used by any of the providers?
// Pass authToken in header by default (unless 'useAuthTokenHeader: false' is set)
if (Object.prototype.hasOwnProperty.call(provider, "useAuthTokenHeader")) {
client.useAuthorizationHeaderforGET(provider.useAuthTokenHeader)
} else {
client.useAuthorizationHeaderforGET(true)
}
try {
const tokens = await client.getOAuthAccessToken(
code,
provider,
pkce.code_verifier
)
let profileData
if (provider.idToken) {
if (!tokens?.id_token) {
throw new OAuthCallbackError("Missing JWT ID Token")
}
// Support services that use OpenID ID Tokens to encode profile data
profileData = jwtDecode(tokens.id_token, { json: true })
} else {
profileData = await client.get(provider, tokens.accessToken, tokens)
}
return getProfile({ profileData, provider, tokens, user })
} catch (error) {
logger.error("OAUTH_GET_ACCESS_TOKEN_ERROR", error, provider.id, code)
throw error
}
}
try {
// Handle OAuth v1.x
// eslint-disable-next-line camelcase
const { oauth_token, oauth_verifier } = req.query
// eslint-disable-next-line camelcase
const { token_secret } = await client.getOAuthRequestToken(provider.params)
const tokens = await client.getOAuthAccessToken(oauth_token, token_secret, oauth_verifier)
const profileData = await client.get(
provider.profileUrl,
tokens.oauth_token,
tokens.oauth_token_secret
)
return getProfile({ profileData, tokens, provider })
} catch (error) {
logger.error("OAUTH_V1_GET_ACCESS_TOKEN_ERROR", error)
throw error
}
}
/**
* //6/30/2020 @geraldnolan added userData parameter to attach additional data to the profileData object
* Returns profile, raw profile and auth provider details
* @param {{
* profileData: object | string
* tokens: {
* accessToken: string
* idToken?: string
* refreshToken?: string
* access_token: string
* expires_in?: string | Date | null
* refresh_token?: string
* id_token?: string
* token?: string
* token_secret?: string
* tokenSecret?: string
* params?: any
* }
* provider: import("../..").Provider
* user?: object
* }} profileParams
*/
async function getProfile({ profileData, tokens, provider, user }) {
try {
// Convert profileData into an object if it's a string
if (typeof profileData === "string" || profileData instanceof String) {
profileData = JSON.parse(profileData)
}
// If a user object is supplied (e.g. Apple provider) add it to the profile object
if (user != null) {
profileData.user = user
}
logger.debug("PROFILE_DATA", profileData)
const profile = await provider.profile(profileData, tokens)
// Return profile, raw profile and auth provider details
return {
profile: {
...profile,
email: profile.email?.toLowerCase() ?? null,
},
account: {
provider: provider.id,
type: provider.type,
id: profile.id,
...tokens,
},
OAuthProfile: profileData,
}
} catch (exception) {
// If we didn't get a response either there was a problem with the provider
// response *or* the user cancelled the action with the provider.
//
// Unfortuately, we can't tell which - at least not in a way that works for
// all providers, so we return an empty object; the user should then be
// redirected back to the sign up page. We log the error to help developers
// who might be trying to debug this when configuring a new provider.
logger.error("OAUTH_PARSE_PROFILE_ERROR", exception, profileData)
return {
profile: null,
account: null,
OAuthProfile: profileData,
}
}
}