fix: get auth token for development (#4295)

This commit is contained in:
Peng Xiao 2023-09-12 13:31:58 +08:00 committed by GitHub
parent 98429bf89e
commit fc76163dd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 131 additions and 34 deletions

View File

@ -178,11 +178,11 @@ const OpenOAuthJwt = () => {
}, [params]); }, [params]);
const channel = schemaToChanel[schema as Schema]; const channel = schemaToChanel[schema as Schema];
if (!currentUser || !currentUser?.token?.token) { if (!currentUser || !currentUser?.token?.sessionToken) {
return null; return null;
} }
const urlToOpen = `${schema}://oauth-jwt?token=${currentUser.token.token}`; const urlToOpen = `${schema}://oauth-jwt?token=${currentUser.token.sessionToken}`;
return <OpenAppImpl urlToOpen={urlToOpen} channel={channel} />; return <OpenAppImpl urlToOpen={urlToOpen} channel={channel} />;
}; };

View File

@ -30,6 +30,9 @@ export class TokenType {
@Field() @Field()
refresh!: string; refresh!: string;
@Field({ nullable: true })
sessionToken?: string;
} }
/** /**
@ -49,18 +52,32 @@ export class AuthResolver {
@Throttle(20, 60) @Throttle(20, 60)
@ResolveField(() => TokenType) @ResolveField(() => TokenType)
async token(@CurrentUser() currentUser: UserType, @Parent() user: UserType) { async token(
@Context() ctx: { req: Request },
@CurrentUser() currentUser: UserType,
@Parent() user: UserType
) {
if (user.id !== currentUser.id) { if (user.id !== currentUser.id) {
throw new BadRequestException('Invalid user'); throw new BadRequestException('Invalid user');
} }
// on production we use session token that is stored in database (strategy = 'database') let sessionToken: string | undefined;
const sessionToken = this.config.node.prod
? await this.auth.getSessionToken(user.id) // only return session if the request is from the same origin & path == /open-app
: this.auth.sign(user); if (
ctx.req.headers.referer &&
ctx.req.headers.host &&
new URL(ctx.req.headers.referer).pathname.startsWith('/open-app') &&
ctx.req.headers.host === new URL(this.config.origin).host
) {
const cookiePrefix = this.config.node.prod ? '__Secure-' : '';
const sessionCookieName = `${cookiePrefix}next-auth.session-token`;
sessionToken = ctx.req.cookies?.[sessionCookieName];
}
return { return {
token: sessionToken, sessionToken,
token: this.auth.sign(user),
refresh: this.auth.refresh(user), refresh: this.auth.refresh(user),
}; };
} }

View File

@ -251,17 +251,4 @@ export class AuthService {
async sendChangeEmail(email: string, callbackUrl: string) { async sendChangeEmail(email: string, callbackUrl: string) {
return this.mailer.sendChangeEmail(email, callbackUrl); return this.mailer.sendChangeEmail(email, callbackUrl);
} }
async getSessionToken(userId: string) {
const session = await this.prisma.session.findFirst({
where: {
userId: userId,
},
});
if (!session) {
throw new BadRequestException(`No session found for user id ${userId}`);
}
return session?.sessionToken;
}
} }

View File

@ -48,6 +48,7 @@ enum NewFeaturesKind {
type TokenType { type TokenType {
token: String! token: String!
refresh: String! refresh: String!
sessionToken: String
} }
type InviteUserType { type InviteUserType {

View File

@ -7,11 +7,13 @@ import { ConfigModule } from '../config';
import { GqlModule } from '../graphql.module'; import { GqlModule } from '../graphql.module';
import { MetricsModule } from '../metrics'; import { MetricsModule } from '../metrics';
import { AuthModule } from '../modules/auth'; import { AuthModule } from '../modules/auth';
import { AuthResolver } from '../modules/auth/resolver';
import { AuthService } from '../modules/auth/service'; import { AuthService } from '../modules/auth/service';
import { PrismaModule } from '../prisma'; import { PrismaModule } from '../prisma';
import { RateLimiterModule } from '../throttler'; import { RateLimiterModule } from '../throttler';
let auth: AuthService; let authService: AuthService;
let authResolver: AuthResolver;
let module: TestingModule; let module: TestingModule;
// cleanup database before each test // cleanup database before each test
@ -31,6 +33,8 @@ test.beforeEach(async () => {
refreshTokenExpiresIn: 1, refreshTokenExpiresIn: 1,
leeway: 1, leeway: 1,
}, },
host: 'example.org',
https: true,
}), }),
PrismaModule, PrismaModule,
GqlModule, GqlModule,
@ -39,7 +43,8 @@ test.beforeEach(async () => {
RateLimiterModule, RateLimiterModule,
], ],
}).compile(); }).compile();
auth = module.get(AuthService); authService = module.get(AuthService);
authResolver = module.get(AuthResolver);
}); });
test.afterEach.always(async () => { test.afterEach.always(async () => {
@ -47,14 +52,14 @@ test.afterEach.always(async () => {
}); });
test('should be able to register and signIn', async t => { test('should be able to register and signIn', async t => {
await auth.signUp('Alex Yang', 'alexyang@example.org', '123456'); await authService.signUp('Alex Yang', 'alexyang@example.org', '123456');
await auth.signIn('alexyang@example.org', '123456'); await authService.signIn('alexyang@example.org', '123456');
t.pass(); t.pass();
}); });
test('should be able to verify', async t => { test('should be able to verify', async t => {
await auth.signUp('Alex Yang', 'alexyang@example.org', '123456'); await authService.signUp('Alex Yang', 'alexyang@example.org', '123456');
await auth.signIn('alexyang@example.org', '123456'); await authService.signIn('alexyang@example.org', '123456');
const date = new Date(); const date = new Date();
const user = { const user = {
@ -66,8 +71,8 @@ test('should be able to verify', async t => {
avatarUrl: '', avatarUrl: '',
}; };
{ {
const token = await auth.sign(user); const token = await authService.sign(user);
const claim = await auth.verify(token); const claim = await authService.verify(token);
t.is(claim.id, '1'); t.is(claim.id, '1');
t.is(claim.name, 'Alex Yang'); t.is(claim.name, 'Alex Yang');
t.is(claim.email, 'alexyang@example.org'); t.is(claim.email, 'alexyang@example.org');
@ -75,8 +80,8 @@ test('should be able to verify', async t => {
t.is(claim.createdAt.toISOString(), date.toISOString()); t.is(claim.createdAt.toISOString(), date.toISOString());
} }
{ {
const token = await auth.refresh(user); const token = await authService.refresh(user);
const claim = await auth.verify(token); const claim = await authService.verify(token);
t.is(claim.id, '1'); t.is(claim.id, '1');
t.is(claim.name, 'Alex Yang'); t.is(claim.name, 'Alex Yang');
t.is(claim.email, 'alexyang@example.org'); t.is(claim.email, 'alexyang@example.org');
@ -84,3 +89,90 @@ test('should be able to verify', async t => {
t.is(claim.createdAt.toISOString(), date.toISOString()); t.is(claim.createdAt.toISOString(), date.toISOString());
} }
}); });
test('should not be able to return token if user is invalid', async t => {
const date = new Date();
const user = {
id: '1',
name: 'Alex Yang',
email: 'alexyang@example.org',
emailVerified: date,
createdAt: date,
avatarUrl: '',
};
const anotherUser = {
id: '2',
name: 'Alex Yang 2',
email: 'alexyang@example.org',
emailVerified: date,
createdAt: date,
avatarUrl: '',
};
await t.throwsAsync(
authResolver.token(
{
req: {
headers: {
referer: 'https://example.org',
host: 'example.org',
},
} as any,
},
user,
anotherUser
),
{
message: 'Invalid user',
}
);
});
test('should not return sessionToken if request headers is invalid', async t => {
const date = new Date();
const user = {
id: '1',
name: 'Alex Yang',
email: 'alexyang@example.org',
emailVerified: date,
createdAt: date,
avatarUrl: '',
};
const result = await authResolver.token(
{
req: {
headers: {},
} as any,
},
user,
user
);
t.is(result.sessionToken, undefined);
});
test('should return valid sessionToken if request headers valid', async t => {
const date = new Date();
const user = {
id: '1',
name: 'Alex Yang',
email: 'alexyang@example.org',
emailVerified: date,
createdAt: date,
avatarUrl: '',
};
const result = await authResolver.token(
{
req: {
headers: {
referer: 'https://example.org/open-app/test',
host: 'example.org',
},
cookies: {
'next-auth.session-token': '123456',
},
} as any,
},
user,
user
);
t.is(result.sessionToken, '123456');
});

View File

@ -7,7 +7,7 @@ query getCurrentUser {
avatarUrl avatarUrl
createdAt createdAt
token { token {
token sessionToken
} }
} }
} }

View File

@ -165,7 +165,7 @@ query getCurrentUser {
avatarUrl avatarUrl
createdAt createdAt
token { token {
token sessionToken
} }
} }
}`, }`,

View File

@ -173,7 +173,7 @@ export type GetCurrentUserQuery = {
emailVerified: string | null; emailVerified: string | null;
avatarUrl: string | null; avatarUrl: string | null;
createdAt: string | null; createdAt: string | null;
token: { __typename?: 'TokenType'; token: string }; token: { __typename?: 'TokenType'; sessionToken: string | null };
}; };
}; };