mirror of
https://github.com/logto-io/logto.git
synced 2024-12-23 20:33:16 -05:00
184 lines
5.6 KiB
TypeScript
184 lines
5.6 KiB
TypeScript
|
import {
|
||
|
createRequester,
|
||
|
fetchOidcConfig,
|
||
|
fetchTokenByAuthorizationCode,
|
||
|
generateSignInUri,
|
||
|
verifyAndParseCodeFromCallbackUri,
|
||
|
} from '@logto/js';
|
||
|
import got from 'got/dist/source';
|
||
|
|
||
|
import api from '@/api';
|
||
|
|
||
|
import { adminConsoleApplicationId, discoveryUrl, logtoUrl, redirectUri } from '../src/constants';
|
||
|
import { LogtoContext } from '../src/logto-context';
|
||
|
import { extractCookie } from '../src/utils';
|
||
|
|
||
|
describe('username and password flow', () => {
|
||
|
const logtoContext = new LogtoContext();
|
||
|
|
||
|
beforeAll(async () => {
|
||
|
await logtoContext.init();
|
||
|
});
|
||
|
|
||
|
it('should fetch OIDC configuration', async () => {
|
||
|
const oidcConfig = await fetchOidcConfig(discoveryUrl, createRequester());
|
||
|
const { authorizationEndpoint, tokenEndpoint } = oidcConfig;
|
||
|
expect(authorizationEndpoint).toBeTruthy();
|
||
|
expect(tokenEndpoint).toBeTruthy();
|
||
|
|
||
|
logtoContext.setData('authorizationEndpoint', authorizationEndpoint);
|
||
|
logtoContext.setData('tokenEndpoint', tokenEndpoint);
|
||
|
});
|
||
|
|
||
|
it('should visit authorization endpoint and get interaction cookie', async () => {
|
||
|
const signInUri = generateSignInUri({
|
||
|
authorizationEndpoint: logtoContext.authorizationEndpoint,
|
||
|
clientId: adminConsoleApplicationId,
|
||
|
redirectUri,
|
||
|
codeChallenge: logtoContext.codeChallenge,
|
||
|
state: logtoContext.state,
|
||
|
});
|
||
|
|
||
|
const response = await got(signInUri, {
|
||
|
followRedirect: false,
|
||
|
});
|
||
|
|
||
|
// Note: this will redirect to the ui sign in page
|
||
|
expect(response.statusCode).toBe(303);
|
||
|
expect(response.headers.location).toBe('/sign-in');
|
||
|
|
||
|
const cookie = extractCookie(response);
|
||
|
expect(cookie).toBeTruthy();
|
||
|
|
||
|
logtoContext.setData('interactionCookie', cookie);
|
||
|
});
|
||
|
|
||
|
it('should register with username and password and redirect to oidc/auth endpoint to start an auth process', async () => {
|
||
|
type RegisterResponse = {
|
||
|
redirectTo: string;
|
||
|
};
|
||
|
|
||
|
const registerResponse = await api
|
||
|
.post('session/register/username-password', {
|
||
|
headers: {
|
||
|
cookie: logtoContext.interactionCookie,
|
||
|
},
|
||
|
json: logtoContext.account,
|
||
|
})
|
||
|
.json<RegisterResponse>();
|
||
|
|
||
|
const { redirectTo: invokeAuthUrl } = registerResponse;
|
||
|
|
||
|
expect(invokeAuthUrl.startsWith(`${logtoUrl}/oidc/auth`)).toBeTruthy();
|
||
|
});
|
||
|
|
||
|
it('should sign in with username and password and redirect to oidc/auth endpoint to start an auth process', async () => {
|
||
|
type SignInResponse = {
|
||
|
redirectTo: string;
|
||
|
};
|
||
|
|
||
|
const signInResponse = await api
|
||
|
.post('session/sign-in/username-password', {
|
||
|
headers: {
|
||
|
cookie: logtoContext.interactionCookie,
|
||
|
},
|
||
|
json: logtoContext.account,
|
||
|
followRedirect: false,
|
||
|
})
|
||
|
.json<SignInResponse>();
|
||
|
|
||
|
const { redirectTo: invokeAuthUrl } = signInResponse;
|
||
|
|
||
|
expect(invokeAuthUrl.startsWith(`${logtoUrl}/oidc/auth`)).toBeTruthy();
|
||
|
|
||
|
logtoContext.setData('nextRedirectTo', invokeAuthUrl);
|
||
|
});
|
||
|
|
||
|
it('should invoke the auth process and redirect to the consent page with session cookie', async () => {
|
||
|
const invokeAuthUrl = logtoContext.nextRedirectTo;
|
||
|
const invokeAuthResponse = await got.get(invokeAuthUrl, {
|
||
|
headers: {
|
||
|
cookie: logtoContext.interactionCookie,
|
||
|
},
|
||
|
followRedirect: false,
|
||
|
});
|
||
|
|
||
|
// Note: Redirect to consent page
|
||
|
expect(invokeAuthResponse).toHaveProperty('statusCode', 303);
|
||
|
expect(invokeAuthResponse.headers.location).toBe('/sign-in/consent');
|
||
|
|
||
|
const cookie = extractCookie(invokeAuthResponse);
|
||
|
expect(cookie).toBeTruthy();
|
||
|
expect(cookie.includes('_session.sig')).toBeTruthy();
|
||
|
|
||
|
logtoContext.setData('interactionCookie', cookie);
|
||
|
});
|
||
|
|
||
|
it('should redirect to oidc/auth endpoint to complete the auth process after consent', async () => {
|
||
|
type ConsentResponse = {
|
||
|
redirectTo: string;
|
||
|
};
|
||
|
|
||
|
const consentResponse = await api
|
||
|
.post('session/consent', {
|
||
|
headers: {
|
||
|
cookie: logtoContext.interactionCookie,
|
||
|
},
|
||
|
followRedirect: false,
|
||
|
})
|
||
|
.json<ConsentResponse>();
|
||
|
|
||
|
const { redirectTo: completeAuthUrl } = consentResponse;
|
||
|
|
||
|
expect(completeAuthUrl.startsWith(`${logtoUrl}/oidc/auth`)).toBeTruthy();
|
||
|
|
||
|
logtoContext.setData('nextRedirectTo', completeAuthUrl);
|
||
|
});
|
||
|
|
||
|
it('should get the authorization code from the callback uri when the auth process is completed', async () => {
|
||
|
const completeAuthUrl = logtoContext.nextRedirectTo;
|
||
|
const authCodeResponse = await got.get(completeAuthUrl, {
|
||
|
headers: {
|
||
|
cookie: logtoContext.interactionCookie,
|
||
|
},
|
||
|
});
|
||
|
|
||
|
expect(authCodeResponse).toHaveProperty('statusCode', 200);
|
||
|
const callbackUri = authCodeResponse.redirectUrls[0];
|
||
|
expect(callbackUri).toBeTruthy();
|
||
|
|
||
|
if (!callbackUri) {
|
||
|
throw new Error('No redirect uri');
|
||
|
}
|
||
|
|
||
|
const authorizationCode = verifyAndParseCodeFromCallbackUri(
|
||
|
callbackUri,
|
||
|
redirectUri,
|
||
|
logtoContext.state
|
||
|
);
|
||
|
expect(authorizationCode).toBeTruthy();
|
||
|
|
||
|
logtoContext.setData('authorizationCode', authorizationCode);
|
||
|
});
|
||
|
|
||
|
it('should fetch token by authorization code', async () => {
|
||
|
const token = await fetchTokenByAuthorizationCode(
|
||
|
{
|
||
|
clientId: adminConsoleApplicationId,
|
||
|
tokenEndpoint: logtoContext.tokenEndpoint,
|
||
|
redirectUri,
|
||
|
codeVerifier: logtoContext.codeVerifier,
|
||
|
code: logtoContext.authorizationCode,
|
||
|
},
|
||
|
createRequester()
|
||
|
);
|
||
|
|
||
|
expect(token).toHaveProperty('accessToken');
|
||
|
expect(token).toHaveProperty('expiresIn');
|
||
|
expect(token).toHaveProperty('idToken');
|
||
|
expect(token).toHaveProperty('refreshToken');
|
||
|
expect(token).toHaveProperty('scope');
|
||
|
expect(token).toHaveProperty('tokenType');
|
||
|
});
|
||
|
});
|