@adonisjs/ally
Version:
Social authentication provider for AdonisJS
242 lines (239 loc) • 7 kB
JavaScript
import {
RedirectRequest
} from "./chunk-NK6X76EQ.js";
import {
E_OAUTH_MISSING_CODE,
E_OAUTH_STATE_MISMATCH
} from "./chunk-TSIMPJ6I.js";
// src/abstract_drivers/oauth1.ts
import { Exception } from "@adonisjs/core/exceptions";
import { Oauth1Client } from "@poppinss/oauth-client/oauth1";
var Oauth1Driver = class extends Oauth1Client {
/**
* Create a new OAuth1 driver instance.
*
* @param ctx - The current HTTP context
* @param config - OAuth1 driver configuration
*/
constructor(ctx, config) {
super(config);
this.ctx = ctx;
this.config = config;
}
ctx;
config;
/**
* OAuth protocol version identifier
*/
version = "oauth1";
/**
* Cached OAuth token and secret values read from cookies
*/
oauthTokenCookieValue;
oauthSecretCookieValue;
/**
* The cookie name for storing the OAuth token secret.
* Automatically derived from the token cookie name.
*
* @returns The cookie name used to persist the OAuth token secret.
*/
get oauthSecretCookieName() {
return `${this.oauthTokenCookieName}_secret`;
}
/**
* Creates a URL builder instance for constructing authorization URLs
* with scope support.
*
* @param url - The base authorization URL
* @returns A redirect request builder for the given URL.
*/
urlBuilder(url) {
return new RedirectRequest(url, this.scopeParamName, this.scopesSeparator);
}
/**
* Loads the OAuth token and secret from encrypted cookies and immediately
* clears the cookies. This must be called by child classes in their
* constructor to enable token verification.
*
* @example
* ```ts
* constructor(ctx: HttpContext, config: DriverConfig) {
* super(ctx, config)
* this.loadState()
* }
* ```
*/
loadState() {
this.oauthTokenCookieValue = this.ctx.request.encryptedCookie(this.oauthTokenCookieName);
this.oauthSecretCookieValue = this.ctx.request.encryptedCookie(this.oauthSecretCookieName);
this.ctx.response.clearCookie(this.oauthTokenCookieName);
this.ctx.response.clearCookie(this.oauthSecretCookieName);
}
/**
* Stores the OAuth token in an encrypted cookie for later use
*
* @param token - The request token to persist.
*/
#persistToken(token) {
this.ctx.response.encryptedCookie(this.oauthTokenCookieName, token, {
sameSite: false,
httpOnly: true
});
}
/**
* Stores the OAuth token secret in an encrypted cookie for later use
*
* @param secret - The request token secret to persist.
*/
#persistSecret(secret) {
this.ctx.response.encryptedCookie(this.oauthSecretCookieName, secret, {
sameSite: false,
httpOnly: true
});
}
/**
* OAuth1 does not support stateless authentication due to the
* three-legged authentication flow requiring token persistence.
*
* @returns This method never returns.
*/
stateless() {
throw new Exception("OAuth1 does not support stateless authorization");
}
/**
* Get the authorization redirect URL without performing the redirect.
* Useful when you need to manually handle the redirect or use the URL
* in a different context.
*
* @param callback - Optional callback to customize the redirect request
* @returns A promise resolving to the authorization URL.
*
* @example
* ```ts
* const url = await ally.use('twitter').redirectUrl()
* ```
*/
async redirectUrl(callback) {
return this.getRedirectUrl(callback);
}
/**
* Redirect the user to the OAuth provider's authorization page.
* The request token is automatically obtained and stored in cookies.
*
* @param callback - Optional callback to customize the redirect request
* @returns A promise that resolves after the redirect response is prepared.
*
* @example
* ```ts
* await ally.use('twitter').redirect()
* ```
*/
async redirect(callback) {
const { token, secret } = await this.getRequestToken();
this.#persistToken(token);
this.#persistSecret(secret);
const url = await this.redirectUrl((request) => {
request.param(this.oauthTokenParamName, token);
if (typeof callback === "function") {
callback(request);
}
});
if ("inertia" in this.ctx && this.ctx.inertia.requestInfo().isInertiaRequest) {
this.ctx.inertia.location(url);
} else {
this.ctx.response.redirect(url);
}
}
/**
* Check if the OAuth token from the callback matches the token
* stored in the cookie.
*
* @returns `true` when the callback token does not match the stored token.
*/
stateMisMatch() {
return this.oauthTokenCookieValue !== this.ctx.request.input(this.oauthTokenParamName);
}
/**
* Check if an error was returned by the OAuth provider.
*
* @returns `true` when an error exists on the callback request.
*/
hasError() {
return !!this.getError();
}
/**
* Get the error code or message returned by the OAuth provider.
* Returns 'unknown_error' if no verifier is present and no error was specified.
*
* @returns The provider error value when present.
*/
getError() {
const error = this.ctx.request.input(this.errorParamName);
if (error) {
return error;
}
if (!this.hasCode()) {
return "unknown_error";
}
return null;
}
/**
* Get the OAuth verifier from the callback request.
*
* @returns The OAuth verifier when present.
*/
getCode() {
return this.ctx.request.input(this.oauthTokenVerifierName, null);
}
/**
* Check if the OAuth verifier is present in the callback request.
*
* @returns `true` when the callback request contains an OAuth verifier.
*/
hasCode() {
return !!this.getCode();
}
/**
* Exchange the request token and verifier for an access token.
* This method validates the token and checks for errors before
* making the request.
*
* @param callback - Optional callback to customize the token request
* @returns A promise resolving to the access token payload.
*
* @example
* ```ts
* const token = await ally.use('twitter').accessToken()
* ```
*/
async accessToken(callback) {
if (this.hasError()) {
throw new E_OAUTH_MISSING_CODE([this.oauthTokenVerifierName]);
}
if (this.stateMisMatch()) {
throw new E_OAUTH_STATE_MISMATCH();
}
return this.getAccessToken(
{ token: this.oauthTokenCookieValue, secret: this.oauthSecretCookieValue },
(request) => {
request.oauth1Param(this.oauthTokenVerifierName, this.getCode());
if (typeof callback === "function") {
callback(request);
}
}
);
}
/**
* Not applicable with OAuth1. Use `userFromTokenAndSecret` instead.
*
* @returns This method never returns.
*/
async userFromToken() {
throw new Exception(
'"userFromToken" is not available with Oauth1. Use "userFromTokenAndSecret" instead'
);
}
};
export {
Oauth1Driver
};