Feat: OAuth2 implicit grant type (#4307)

* add: implicit grant type
This commit is contained in:
naman-bruno
2025-07-11 13:55:03 +05:30
committed by GitHub
parent 668fbfb0e0
commit a68833089f
20 changed files with 919 additions and 56 deletions

View File

@@ -67,6 +67,44 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
autoFetchToken,
} = oAuth;
const url = requestCopy?.oauth2?.accessTokenUrl;
// Validate required fields
if (!authorizationUrl) {
return {
error: 'Authorization URL is required for OAuth2 authorization code flow',
credentials: null,
url,
credentialsId
};
}
if (!url) {
return {
error: 'Access Token URL is required for OAuth2 authorization code flow',
credentials: null,
url: authorizationUrl,
credentialsId
};
}
if (!callbackUrl) {
return {
error: 'Callback URL is required for OAuth2 authorization code flow',
credentials: null,
url,
credentialsId
};
}
if (!clientId) {
return {
error: 'Client ID is required for OAuth2 authorization code flow',
credentials: null,
url,
credentialsId
};
}
if (!forceFetch) {
const storedCredentials = getStoredOauth2Credentials({ collectionUid, url, credentialsId });
@@ -279,6 +317,34 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
const url = requestCopy?.oauth2?.accessTokenUrl;
// Validate required fields
if (!url) {
return {
error: 'Access Token URL is required for OAuth2 client credentials flow',
credentials: null,
url,
credentialsId
};
}
if (!clientId) {
return {
error: 'Client ID is required for OAuth2 client credentials flow',
credentials: null,
url,
credentialsId
};
}
if (!clientSecret) {
return {
error: 'Client Secret is required for OAuth2 client credentials flow',
credentials: null,
url,
credentialsId
};
}
if (!forceFetch) {
const storedCredentials = getStoredOauth2Credentials({ collectionUid, url, credentialsId });
@@ -447,6 +513,43 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
} = oAuth;
const url = requestCopy?.oauth2?.accessTokenUrl;
// Validate required fields
if (!url) {
return {
error: 'Access Token URL is required for OAuth2 password credentials flow',
credentials: null,
url,
credentialsId
};
}
if (!username) {
return {
error: 'Username is required for OAuth2 password credentials flow',
credentials: null,
url,
credentialsId
};
}
if (!password) {
return {
error: 'Password is required for OAuth2 password credentials flow',
credentials: null,
url,
credentialsId
};
}
if (!clientId) {
return {
error: 'Client ID is required for OAuth2 password credentials flow',
credentials: null,
url,
credentialsId
};
}
if (!forceFetch) {
const storedCredentials = getStoredOauth2Credentials({ collectionUid, url, credentialsId });
@@ -726,10 +829,167 @@ const generateCodeChallenge = (codeVerifier) => {
return base64Hash;
};
const getOAuth2TokenUsingImplicitGrant = async ({ request, collectionUid, forceFetch = false }) => {
const { oauth2 = {} } = request;
const {
authorizationUrl,
clientId,
scope,
state = '',
callbackUrl,
credentialsId = 'credentials',
autoFetchToken = true
} = oauth2;
// Validate required fields
if (!authorizationUrl) {
return {
error: 'Authorization URL is required for OAuth2 implicit flow',
credentials: null,
url: authorizationUrl,
credentialsId
};
}
if (!callbackUrl) {
return {
error: 'Callback URL is required for OAuth2 implicit flow',
credentials: null,
url: authorizationUrl,
credentialsId
};
}
// Check if we already have valid credentials
if (!forceFetch) {
try {
const storedCredentials = getStoredOauth2Credentials({
collectionUid,
url: authorizationUrl,
credentialsId
});
if (storedCredentials) {
// Token exists
if (!isTokenExpired(storedCredentials)) {
// Token is valid, use it
return {
collectionUid,
credentials: storedCredentials,
url: authorizationUrl,
credentialsId
};
} else {
// Token is expired - unlike other grant types, implicit flow doesn't support refresh tokens
if (autoFetchToken) {
// Proceed to fetch new token
clearOauth2Credentials({ collectionUid, url: authorizationUrl, credentialsId });
} else {
// Proceed with expired token
return {
collectionUid,
credentials: storedCredentials,
url: authorizationUrl,
credentialsId
};
}
}
} else {
// No stored credentials
if (!autoFetchToken) {
// Don't fetch token if autoFetchToken is disabled
return {
collectionUid,
credentials: null,
url: authorizationUrl,
credentialsId
};
}
// Otherwise proceed to fetch new token
}
} catch (error) {
console.error('Error retrieving oauth2 credentials from cache', error);
clearOauth2Credentials({ collectionUid, url: authorizationUrl, credentialsId });
}
}
const authorizationUrlWithQueryParams = new URL(authorizationUrl);
authorizationUrlWithQueryParams.searchParams.append('response_type', 'token');
authorizationUrlWithQueryParams.searchParams.append('client_id', clientId);
authorizationUrlWithQueryParams.searchParams.append('redirect_uri', callbackUrl);
if (scope) {
authorizationUrlWithQueryParams.searchParams.append('scope', scope);
}
if (state) {
authorizationUrlWithQueryParams.searchParams.append('state', state);
}
const authorizeUrl = authorizationUrlWithQueryParams.toString();
try {
const { implicitTokens, debugInfo } = await authorizeUserInWindow({
authorizeUrl,
callbackUrl,
session: oauth2Store.getSessionIdOfCollection({ collectionUid, url: authorizationUrl }),
grantType: 'implicit'
});
if (!implicitTokens || !implicitTokens.access_token) {
return {
error: 'No access token received from authorization server',
credentials: null,
url: authorizationUrl,
credentialsId,
debugInfo
};
}
const credentials = {
access_token: implicitTokens.access_token,
token_type: implicitTokens.token_type || 'Bearer',
state: implicitTokens.state || '',
...(implicitTokens.expires_in ? { expires_in: parseInt(implicitTokens.expires_in) } : {}),
created_at: Date.now()
};
if (implicitTokens.scope) {
credentials.scope = implicitTokens.scope;
}
// Store the credentials
persistOauth2Credentials({
collectionUid,
url: authorizationUrl,
credentials,
credentialsId
});
return {
collectionUid,
credentials,
url: authorizationUrl,
credentialsId,
debugInfo
};
} catch (error) {
return {
error: error.message || 'Failed to obtain token',
credentials: null,
url: authorizationUrl,
credentialsId
};
}
};
module.exports = {
persistOauth2Credentials,
clearOauth2Credentials,
getStoredOauth2Credentials,
getOAuth2TokenUsingAuthorizationCode,
getOAuth2AuthorizationCode,
getOAuth2TokenUsingClientCredentials,
getOAuth2TokenUsingPasswordCredentials,
refreshOauth2Token
getOAuth2TokenUsingImplicitGrant,
refreshOauth2Token,
generateCodeVerifier,
generateCodeChallenge
};