@appforgeapps/shieldforge-graphql 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # @shieldforge/graphql
2
+
3
+ GraphQL schema definitions and resolvers for authentication.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @shieldforge/graphql
9
+ ```
10
+
11
+ Peer dependencies:
12
+ ```bash
13
+ npm install graphql
14
+ ```
15
+
16
+ ## Quick Start
17
+
18
+ ### Backend Setup
19
+
20
+ ```typescript
21
+ import { createResolvers, typeDefs } from '@shieldforge/graphql';
22
+ import { ShieldForge } from '@shieldforge/core';
23
+
24
+ const auth = new ShieldForge({
25
+ jwtSecret: process.env.JWT_SECRET!,
26
+ });
27
+
28
+ // Implement data source
29
+ const dataSource = {
30
+ getUserById: async (id) => await db.user.findUnique({ where: { id } }),
31
+ getUserByEmail: async (email) => await db.user.findUnique({ where: { email } }),
32
+ createUser: async (input) => await db.user.create({ data: input }),
33
+ updateUser: async (id, input) => await db.user.update({ where: { id }, data: input }),
34
+ createPasswordReset: async (userId, code, expiresAt) => {
35
+ await db.passwordReset.create({ data: { userId, code, expiresAt } });
36
+ },
37
+ getPasswordReset: async (code) => {
38
+ return await db.passwordReset.findUnique({ where: { code } });
39
+ },
40
+ deletePasswordReset: async (code) => {
41
+ await db.passwordReset.delete({ where: { code } });
42
+ },
43
+ };
44
+
45
+ // Create resolvers
46
+ const resolvers = createResolvers({
47
+ dataSource,
48
+ auth: {
49
+ hashPassword: (password) => auth.hashPassword(password),
50
+ verifyPassword: (password, hash) => auth.verifyPassword(password, hash),
51
+ generateToken: (payload) => auth.generateToken(payload),
52
+ verifyToken: (token) => auth.verifyToken(token),
53
+ calculatePasswordStrength: (password) => auth.calculatePasswordStrength(password),
54
+ sanitizeUser: (user) => auth.sanitizeUser(user),
55
+ generateResetCode: () => auth.generateResetCode(),
56
+ sendPasswordResetEmail: (to, code) => auth.sendPasswordResetEmail(to, code),
57
+ },
58
+ });
59
+
60
+ // Use with Apollo Server
61
+ import { ApolloServer } from '@apollo/server';
62
+
63
+ const server = new ApolloServer({
64
+ typeDefs,
65
+ resolvers,
66
+ context: ({ req }) => {
67
+ const token = req.headers.authorization?.replace('Bearer ', '');
68
+ if (token) {
69
+ try {
70
+ const payload = auth.verifyToken(token);
71
+ return { userId: payload.userId, token };
72
+ } catch (error) {
73
+ return {};
74
+ }
75
+ }
76
+ return {};
77
+ },
78
+ });
79
+ ```
80
+
81
+ ### Frontend Usage
82
+
83
+ ```tsx
84
+ import { LOGIN_MUTATION, REGISTER_MUTATION, ME_QUERY } from '@shieldforge/graphql';
85
+ import { useMutation, useQuery } from '@apollo/client';
86
+ import { useAuth } from '@shieldforge/react';
87
+
88
+ function LoginForm() {
89
+ const [login, { loading, error }] = useMutation(LOGIN_MUTATION);
90
+ const { login: authLogin } = useAuth();
91
+
92
+ const handleSubmit = async (email: string, password: string) => {
93
+ const { data } = await login({
94
+ variables: { input: { email, password } }
95
+ });
96
+
97
+ authLogin(data.login.token, data.login.user);
98
+ };
99
+
100
+ return (/* your form */);
101
+ }
102
+
103
+ function Profile() {
104
+ const { data, loading } = useQuery(ME_QUERY);
105
+
106
+ if (loading) return <div>Loading...</div>;
107
+
108
+ return <div>Email: {data.me.email}</div>;
109
+ }
110
+ ```
111
+
112
+ ## Type Definitions
113
+
114
+ The package exports complete GraphQL schema:
115
+
116
+ - `User` type
117
+ - `AuthPayload` type
118
+ - `LoginInput`, `RegisterInput`, `UpdateProfileInput`, `UpdatePasswordInput`
119
+ - `Query.me` - Get current user
120
+ - `Query.checkPasswordStrength` - Check password strength
121
+ - `Mutation.login` - Login user
122
+ - `Mutation.register` - Register user
123
+ - `Mutation.logout` - Logout user
124
+ - `Mutation.updateProfile` - Update user profile
125
+ - `Mutation.updatePassword` - Change password
126
+ - `Mutation.requestPasswordReset` - Request password reset
127
+ - `Mutation.resetPassword` - Reset password with code
128
+
129
+ ## Documents
130
+
131
+ Pre-built query/mutation strings:
132
+
133
+ ```typescript
134
+ import {
135
+ LOGIN_MUTATION,
136
+ REGISTER_MUTATION,
137
+ LOGOUT_MUTATION,
138
+ ME_QUERY,
139
+ UPDATE_PROFILE_MUTATION,
140
+ UPDATE_PASSWORD_MUTATION,
141
+ REQUEST_PASSWORD_RESET_MUTATION,
142
+ RESET_PASSWORD_MUTATION,
143
+ CHECK_PASSWORD_STRENGTH_QUERY,
144
+ USER_FIELDS_FRAGMENT,
145
+ AUTH_PAYLOAD_FRAGMENT,
146
+ } from '@shieldforge/graphql';
147
+ ```
148
+
149
+ ## Extending the Schema
150
+
151
+ You can extend the User type with your own fields:
152
+
153
+ ```graphql
154
+ extend type User {
155
+ customField: String
156
+ anotherField: Int
157
+ }
158
+ ```
159
+
160
+ Then update your data source to return the additional fields.
161
+
162
+ ## Data Source Interface
163
+
164
+ ```typescript
165
+ interface AuthDataSource {
166
+ getUserById(id: string): Promise<User | null>;
167
+ getUserByEmail(email: string): Promise<User | null>;
168
+ createUser(input: RegisterInput & { passwordHash: string }): Promise<User>;
169
+ updateUser(id: string, input: Partial<User>): Promise<User>;
170
+ createPasswordReset(userId: string, code: string, expiresAt: Date): Promise<void>;
171
+ getPasswordReset(code: string): Promise<{ userId: string; expiresAt: Date } | null>;
172
+ deletePasswordReset(code: string): Promise<void>;
173
+ }
174
+ ```
175
+
176
+ ## License
177
+
178
+ MIT
@@ -0,0 +1,23 @@
1
+ /**
2
+ * GraphQL query and mutation documents for Apollo Client
3
+ * These can be used directly with Apollo Client's useQuery and useMutation hooks
4
+ */
5
+ export declare const LOGIN_MUTATION = "\n mutation Login($input: LoginInput!) {\n login(input: $input) {\n user {\n id\n email\n username\n name\n accountStatus\n emailVerified\n createdAt\n updatedAt\n }\n token\n }\n }\n";
6
+ export declare const REGISTER_MUTATION = "\n mutation Register($input: RegisterInput!) {\n register(input: $input) {\n user {\n id\n email\n username\n name\n accountStatus\n emailVerified\n createdAt\n updatedAt\n }\n token\n }\n }\n";
7
+ export declare const LOGOUT_MUTATION = "\n mutation Logout {\n logout\n }\n";
8
+ export declare const ME_QUERY = "\n query Me {\n me {\n id\n email\n username\n name\n accountStatus\n emailVerified\n createdAt\n updatedAt\n }\n }\n";
9
+ export declare const UPDATE_PROFILE_MUTATION = "\n mutation UpdateProfile($input: UpdateProfileInput!) {\n updateProfile(input: $input) {\n id\n email\n username\n name\n accountStatus\n emailVerified\n createdAt\n updatedAt\n }\n }\n";
10
+ export declare const UPDATE_PASSWORD_MUTATION = "\n mutation UpdatePassword($input: UpdatePasswordInput!) {\n updatePassword(input: $input)\n }\n";
11
+ export declare const REQUEST_PASSWORD_RESET_MUTATION = "\n mutation RequestPasswordReset($email: String!) {\n requestPasswordReset(email: $email)\n }\n";
12
+ export declare const RESET_PASSWORD_MUTATION = "\n mutation ResetPassword($code: String!, $newPassword: String!) {\n resetPassword(code: $code, newPassword: $newPassword)\n }\n";
13
+ export declare const CHECK_PASSWORD_STRENGTH_QUERY = "\n query CheckPasswordStrength($password: String!) {\n checkPasswordStrength(password: $password) {\n score\n feedback\n }\n }\n";
14
+ /**
15
+ * Fragment for User fields
16
+ * Can be composed into other queries
17
+ */
18
+ export declare const USER_FIELDS_FRAGMENT = "\n fragment UserFields on User {\n id\n email\n username\n name\n accountStatus\n emailVerified\n createdAt\n updatedAt\n }\n";
19
+ /**
20
+ * Fragment for AuthPayload fields
21
+ */
22
+ export declare const AUTH_PAYLOAD_FRAGMENT = "\n fragment AuthPayloadFields on AuthPayload {\n user {\n ...UserFields\n }\n token\n }\n \n fragment UserFields on User {\n id\n email\n username\n name\n accountStatus\n emailVerified\n createdAt\n updatedAt\n }\n\n";
23
+ //# sourceMappingURL=documents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../src/documents.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,cAAc,2QAgB1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,oRAgB7B,CAAC;AAEF,eAAO,MAAM,eAAe,6CAI3B,CAAC;AAEF,eAAO,MAAM,QAAQ,0KAapB,CAAC;AAEF,eAAO,MAAM,uBAAuB,+OAanC,CAAC;AAEF,eAAO,MAAM,wBAAwB,0GAIpC,CAAC;AAEF,eAAO,MAAM,+BAA+B,yGAI3C,CAAC;AAEF,eAAO,MAAM,uBAAuB,0IAInC,CAAC;AAEF,eAAO,MAAM,6BAA6B,uJAOzC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,4JAWhC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,yQAQjC,CAAC"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * GraphQL query and mutation documents for Apollo Client
3
+ * These can be used directly with Apollo Client's useQuery and useMutation hooks
4
+ */
5
+ export const LOGIN_MUTATION = `
6
+ mutation Login($input: LoginInput!) {
7
+ login(input: $input) {
8
+ user {
9
+ id
10
+ email
11
+ username
12
+ name
13
+ accountStatus
14
+ emailVerified
15
+ createdAt
16
+ updatedAt
17
+ }
18
+ token
19
+ }
20
+ }
21
+ `;
22
+ export const REGISTER_MUTATION = `
23
+ mutation Register($input: RegisterInput!) {
24
+ register(input: $input) {
25
+ user {
26
+ id
27
+ email
28
+ username
29
+ name
30
+ accountStatus
31
+ emailVerified
32
+ createdAt
33
+ updatedAt
34
+ }
35
+ token
36
+ }
37
+ }
38
+ `;
39
+ export const LOGOUT_MUTATION = `
40
+ mutation Logout {
41
+ logout
42
+ }
43
+ `;
44
+ export const ME_QUERY = `
45
+ query Me {
46
+ me {
47
+ id
48
+ email
49
+ username
50
+ name
51
+ accountStatus
52
+ emailVerified
53
+ createdAt
54
+ updatedAt
55
+ }
56
+ }
57
+ `;
58
+ export const UPDATE_PROFILE_MUTATION = `
59
+ mutation UpdateProfile($input: UpdateProfileInput!) {
60
+ updateProfile(input: $input) {
61
+ id
62
+ email
63
+ username
64
+ name
65
+ accountStatus
66
+ emailVerified
67
+ createdAt
68
+ updatedAt
69
+ }
70
+ }
71
+ `;
72
+ export const UPDATE_PASSWORD_MUTATION = `
73
+ mutation UpdatePassword($input: UpdatePasswordInput!) {
74
+ updatePassword(input: $input)
75
+ }
76
+ `;
77
+ export const REQUEST_PASSWORD_RESET_MUTATION = `
78
+ mutation RequestPasswordReset($email: String!) {
79
+ requestPasswordReset(email: $email)
80
+ }
81
+ `;
82
+ export const RESET_PASSWORD_MUTATION = `
83
+ mutation ResetPassword($code: String!, $newPassword: String!) {
84
+ resetPassword(code: $code, newPassword: $newPassword)
85
+ }
86
+ `;
87
+ export const CHECK_PASSWORD_STRENGTH_QUERY = `
88
+ query CheckPasswordStrength($password: String!) {
89
+ checkPasswordStrength(password: $password) {
90
+ score
91
+ feedback
92
+ }
93
+ }
94
+ `;
95
+ /**
96
+ * Fragment for User fields
97
+ * Can be composed into other queries
98
+ */
99
+ export const USER_FIELDS_FRAGMENT = `
100
+ fragment UserFields on User {
101
+ id
102
+ email
103
+ username
104
+ name
105
+ accountStatus
106
+ emailVerified
107
+ createdAt
108
+ updatedAt
109
+ }
110
+ `;
111
+ /**
112
+ * Fragment for AuthPayload fields
113
+ */
114
+ export const AUTH_PAYLOAD_FRAGMENT = `
115
+ fragment AuthPayloadFields on AuthPayload {
116
+ user {
117
+ ...UserFields
118
+ }
119
+ token
120
+ }
121
+ ${USER_FIELDS_FRAGMENT}
122
+ `;
123
+ //# sourceMappingURL=documents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents.js","sourceRoot":"","sources":["../src/documents.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;CAgB7B,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;CAgBhC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG;;;;CAI9B,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;CAavB,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;CAatC,CAAC;AAEF,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;CAIvC,CAAC;AAEF,MAAM,CAAC,MAAM,+BAA+B,GAAG;;;;CAI9C,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;CAItC,CAAC;AAEF,MAAM,CAAC,MAAM,6BAA6B,GAAG;;;;;;;CAO5C,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;CAWnC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;IAOjC,oBAAoB;CACvB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { typeDefs } from './typeDefs';
2
+ export { createResolvers } from './resolvers';
3
+ export type { AuthDataSource, AuthContext, ResolverDependencies } from './resolvers';
4
+ export { LOGIN_MUTATION, REGISTER_MUTATION, LOGOUT_MUTATION, ME_QUERY, UPDATE_PROFILE_MUTATION, UPDATE_PASSWORD_MUTATION, REQUEST_PASSWORD_RESET_MUTATION, RESET_PASSWORD_MUTATION, CHECK_PASSWORD_STRENGTH_QUERY, USER_FIELDS_FRAGMENT, AUTH_PAYLOAD_FRAGMENT, } from './documents';
5
+ export type { User, AuthUser, LoginInput, RegisterInput, UpdateProfileInput, UpdatePasswordInput, AuthPayload, PasswordStrength, } from '@appforgeapps/shieldforge-types';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAErF,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,QAAQ,EACR,uBAAuB,EACvB,wBAAwB,EACxB,+BAA+B,EAC/B,uBAAuB,EACvB,6BAA6B,EAC7B,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AAGrB,YAAY,EACV,IAAI,EACJ,QAAQ,EACR,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,WAAW,EACX,gBAAgB,GACjB,MAAM,iCAAiC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { typeDefs } from './typeDefs';
2
+ export { createResolvers } from './resolvers';
3
+ export { LOGIN_MUTATION, REGISTER_MUTATION, LOGOUT_MUTATION, ME_QUERY, UPDATE_PROFILE_MUTATION, UPDATE_PASSWORD_MUTATION, REQUEST_PASSWORD_RESET_MUTATION, RESET_PASSWORD_MUTATION, CHECK_PASSWORD_STRENGTH_QUERY, USER_FIELDS_FRAGMENT, AUTH_PAYLOAD_FRAGMENT, } from './documents';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,QAAQ,EACR,uBAAuB,EACvB,wBAAwB,EACxB,+BAA+B,EAC/B,uBAAuB,EACvB,6BAA6B,EAC7B,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,81 @@
1
+ import { User, AuthUser, LoginInput, RegisterInput, UpdateProfileInput, UpdatePasswordInput, AuthPayload, PasswordStrength } from '@appforgeapps/shieldforge-types';
2
+ /**
3
+ * Data source functions that must be implemented by the consumer
4
+ */
5
+ export interface AuthDataSource {
6
+ getUserById(id: string): Promise<User | null>;
7
+ getUserByEmail(email: string): Promise<User | null>;
8
+ createUser(input: RegisterInput & {
9
+ passwordHash: string;
10
+ }): Promise<User>;
11
+ updateUser(id: string, input: Partial<User>): Promise<User>;
12
+ createPasswordReset(userId: string, code: string, expiresAt: Date): Promise<void>;
13
+ getPasswordReset(code: string): Promise<{
14
+ userId: string;
15
+ expiresAt: Date;
16
+ } | null>;
17
+ deletePasswordReset(code: string): Promise<void>;
18
+ }
19
+ /**
20
+ * Context interface that must be provided to resolvers
21
+ */
22
+ export interface AuthContext {
23
+ userId?: string;
24
+ token?: string;
25
+ }
26
+ /**
27
+ * Resolver dependencies
28
+ */
29
+ export interface ResolverDependencies {
30
+ dataSource: AuthDataSource;
31
+ auth: {
32
+ hashPassword(password: string): Promise<string>;
33
+ verifyPassword(password: string, hash: string): Promise<boolean>;
34
+ generateToken(payload: {
35
+ userId: string;
36
+ email: string;
37
+ }): string;
38
+ verifyToken(token: string): {
39
+ userId: string;
40
+ email: string;
41
+ };
42
+ calculatePasswordStrength(password: string): PasswordStrength;
43
+ sanitizeUser(user: User): AuthUser;
44
+ generateResetCode(length?: number): string;
45
+ sendPasswordResetEmail(to: string, code: string, resetUrl?: string): Promise<void>;
46
+ };
47
+ }
48
+ /**
49
+ * Create GraphQL resolvers with dependency injection
50
+ */
51
+ export declare function createResolvers(deps: ResolverDependencies): {
52
+ Query: {
53
+ me: (_parent: any, _args: any, context: AuthContext) => Promise<AuthUser | null>;
54
+ checkPasswordStrength: (_parent: any, args: {
55
+ password: string;
56
+ }) => PasswordStrength;
57
+ };
58
+ Mutation: {
59
+ login: (_parent: any, args: {
60
+ input: LoginInput;
61
+ }) => Promise<AuthPayload>;
62
+ register: (_parent: any, args: {
63
+ input: RegisterInput;
64
+ }) => Promise<AuthPayload>;
65
+ logout: (_parent: any, _args: any, _context: AuthContext) => boolean;
66
+ updateProfile: (_parent: any, args: {
67
+ input: UpdateProfileInput;
68
+ }, context: AuthContext) => Promise<AuthUser>;
69
+ updatePassword: (_parent: any, args: {
70
+ input: UpdatePasswordInput;
71
+ }, context: AuthContext) => Promise<boolean>;
72
+ requestPasswordReset: (_parent: any, args: {
73
+ email: string;
74
+ }) => Promise<boolean>;
75
+ resetPassword: (_parent: any, args: {
76
+ code: string;
77
+ newPassword: string;
78
+ }) => Promise<boolean>;
79
+ };
80
+ };
81
+ //# sourceMappingURL=resolvers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../src/resolvers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Create GraphQL resolvers with dependency injection
3
+ */
4
+ export function createResolvers(deps) {
5
+ const { dataSource, auth } = deps;
6
+ return {
7
+ Query: {
8
+ me: async (_parent, _args, context) => {
9
+ if (!context.userId) {
10
+ return null;
11
+ }
12
+ const user = await dataSource.getUserById(context.userId);
13
+ return user ? auth.sanitizeUser(user) : null;
14
+ },
15
+ checkPasswordStrength: (_parent, args) => {
16
+ return auth.calculatePasswordStrength(args.password);
17
+ },
18
+ },
19
+ Mutation: {
20
+ login: async (_parent, args) => {
21
+ const { email, password } = args.input;
22
+ const user = await dataSource.getUserByEmail(email);
23
+ if (!user || !user.passwordHash) {
24
+ throw new Error('Invalid email or password');
25
+ }
26
+ const isValid = await auth.verifyPassword(password, user.passwordHash);
27
+ if (!isValid) {
28
+ throw new Error('Invalid email or password');
29
+ }
30
+ const token = auth.generateToken({ userId: user.id, email: user.email });
31
+ return {
32
+ user: auth.sanitizeUser(user),
33
+ token,
34
+ };
35
+ },
36
+ register: async (_parent, args) => {
37
+ const { email, password, username, name } = args.input;
38
+ // Check if user already exists
39
+ const existingUser = await dataSource.getUserByEmail(email);
40
+ if (existingUser) {
41
+ throw new Error('User with this email already exists');
42
+ }
43
+ // Validate password strength
44
+ const strength = auth.calculatePasswordStrength(password);
45
+ if (strength.score < 2) {
46
+ throw new Error(`Password is too weak: ${strength.feedback.join(', ')}`);
47
+ }
48
+ // Hash password and create user
49
+ const passwordHash = await auth.hashPassword(password);
50
+ const user = await dataSource.createUser({
51
+ email,
52
+ password,
53
+ username,
54
+ name,
55
+ passwordHash,
56
+ });
57
+ const token = auth.generateToken({ userId: user.id, email: user.email });
58
+ return {
59
+ user: auth.sanitizeUser(user),
60
+ token,
61
+ };
62
+ },
63
+ logout: (_parent, _args, _context) => {
64
+ // Logout is typically handled client-side by removing the token
65
+ // Server-side can optionally invalidate the token if using a token blacklist
66
+ return true;
67
+ },
68
+ updateProfile: async (_parent, args, context) => {
69
+ if (!context.userId) {
70
+ throw new Error('Not authenticated');
71
+ }
72
+ const user = await dataSource.updateUser(context.userId, args.input);
73
+ return auth.sanitizeUser(user);
74
+ },
75
+ updatePassword: async (_parent, args, context) => {
76
+ if (!context.userId) {
77
+ throw new Error('Not authenticated');
78
+ }
79
+ const user = await dataSource.getUserById(context.userId);
80
+ if (!user || !user.passwordHash) {
81
+ throw new Error('User not found');
82
+ }
83
+ const isValid = await auth.verifyPassword(args.input.currentPassword, user.passwordHash);
84
+ if (!isValid) {
85
+ throw new Error('Current password is incorrect');
86
+ }
87
+ const strength = auth.calculatePasswordStrength(args.input.newPassword);
88
+ if (strength.score < 2) {
89
+ throw new Error(`New password is too weak: ${strength.feedback.join(', ')}`);
90
+ }
91
+ const newPasswordHash = await auth.hashPassword(args.input.newPassword);
92
+ await dataSource.updateUser(context.userId, { passwordHash: newPasswordHash });
93
+ return true;
94
+ },
95
+ requestPasswordReset: async (_parent, args) => {
96
+ const user = await dataSource.getUserByEmail(args.email);
97
+ if (!user) {
98
+ // Don't reveal whether the email exists
99
+ return true;
100
+ }
101
+ const resetCode = auth.generateResetCode();
102
+ const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
103
+ await dataSource.createPasswordReset(user.id, resetCode, expiresAt);
104
+ await auth.sendPasswordResetEmail(user.email, resetCode);
105
+ return true;
106
+ },
107
+ resetPassword: async (_parent, args) => {
108
+ const reset = await dataSource.getPasswordReset(args.code);
109
+ if (!reset) {
110
+ throw new Error('Invalid or expired reset code');
111
+ }
112
+ if (new Date() > reset.expiresAt) {
113
+ await dataSource.deletePasswordReset(args.code);
114
+ throw new Error('Reset code has expired');
115
+ }
116
+ const strength = auth.calculatePasswordStrength(args.newPassword);
117
+ if (strength.score < 2) {
118
+ throw new Error(`New password is too weak: ${strength.feedback.join(', ')}`);
119
+ }
120
+ const newPasswordHash = await auth.hashPassword(args.newPassword);
121
+ await dataSource.updateUser(reset.userId, { passwordHash: newPasswordHash });
122
+ await dataSource.deletePasswordReset(args.code);
123
+ return true;
124
+ },
125
+ },
126
+ };
127
+ }
128
+ //# sourceMappingURL=resolvers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolvers.js","sourceRoot":"","sources":["../src/resolvers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * GraphQL type definitions for authentication
3
+ */
4
+ export declare const typeDefs = "\n enum UserAccountStatus {\n ACTIVE\n INACTIVE\n SUSPENDED\n PENDING\n }\n\n type User {\n id: ID!\n email: String!\n username: String\n name: String\n accountStatus: UserAccountStatus\n emailVerified: Boolean\n createdAt: String\n updatedAt: String\n }\n\n type AuthPayload {\n user: User!\n token: String!\n }\n\n input LoginInput {\n email: String!\n password: String!\n }\n\n input RegisterInput {\n email: String!\n password: String!\n username: String\n name: String\n }\n\n input UpdateProfileInput {\n username: String\n name: String\n email: String\n }\n\n input UpdatePasswordInput {\n currentPassword: String!\n newPassword: String!\n }\n\n type PasswordStrength {\n score: Int!\n feedback: [String!]!\n }\n\n type Query {\n me: User\n checkPasswordStrength(password: String!): PasswordStrength!\n }\n\n type Mutation {\n login(input: LoginInput!): AuthPayload!\n register(input: RegisterInput!): AuthPayload!\n logout: Boolean!\n updateProfile(input: UpdateProfileInput!): User!\n updatePassword(input: UpdatePasswordInput!): Boolean!\n requestPasswordReset(email: String!): Boolean!\n resetPassword(code: String!, newPassword: String!): Boolean!\n }\n";
5
+ //# sourceMappingURL=typeDefs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typeDefs.d.ts","sourceRoot":"","sources":["../src/typeDefs.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,QAAQ,kxCAkEpB,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * GraphQL type definitions for authentication
3
+ */
4
+ export const typeDefs = `
5
+ enum UserAccountStatus {
6
+ ACTIVE
7
+ INACTIVE
8
+ SUSPENDED
9
+ PENDING
10
+ }
11
+
12
+ type User {
13
+ id: ID!
14
+ email: String!
15
+ username: String
16
+ name: String
17
+ accountStatus: UserAccountStatus
18
+ emailVerified: Boolean
19
+ createdAt: String
20
+ updatedAt: String
21
+ }
22
+
23
+ type AuthPayload {
24
+ user: User!
25
+ token: String!
26
+ }
27
+
28
+ input LoginInput {
29
+ email: String!
30
+ password: String!
31
+ }
32
+
33
+ input RegisterInput {
34
+ email: String!
35
+ password: String!
36
+ username: String
37
+ name: String
38
+ }
39
+
40
+ input UpdateProfileInput {
41
+ username: String
42
+ name: String
43
+ email: String
44
+ }
45
+
46
+ input UpdatePasswordInput {
47
+ currentPassword: String!
48
+ newPassword: String!
49
+ }
50
+
51
+ type PasswordStrength {
52
+ score: Int!
53
+ feedback: [String!]!
54
+ }
55
+
56
+ type Query {
57
+ me: User
58
+ checkPasswordStrength(password: String!): PasswordStrength!
59
+ }
60
+
61
+ type Mutation {
62
+ login(input: LoginInput!): AuthPayload!
63
+ register(input: RegisterInput!): AuthPayload!
64
+ logout: Boolean!
65
+ updateProfile(input: UpdateProfileInput!): User!
66
+ updatePassword(input: UpdatePasswordInput!): Boolean!
67
+ requestPasswordReset(email: String!): Boolean!
68
+ resetPassword(code: String!, newPassword: String!): Boolean!
69
+ }
70
+ `;
71
+ //# sourceMappingURL=typeDefs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typeDefs.js","sourceRoot":"","sources":["../src/typeDefs.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkEvB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@appforgeapps/shieldforge-graphql",
3
+ "version": "0.0.5",
4
+ "description": "GraphQL schema definitions and resolvers for ShieldForge authentication",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/chriscase/ShieldForge.git",
8
+ "directory": "packages/graphql"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "files": [
16
+ "dist",
17
+ "src",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
23
+ "test": "echo \"No tests specified\"",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "graphql",
28
+ "authentication",
29
+ "auth",
30
+ "schema",
31
+ "resolvers",
32
+ "shieldforge"
33
+ ],
34
+ "author": "chriscase",
35
+ "license": "MIT",
36
+ "dependencies": {
37
+ "@appforgeapps/shieldforge-types": "*"
38
+ },
39
+ "peerDependencies": {
40
+ "graphql": "^15.0.0 || ^16.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^20.10.0",
44
+ "graphql": "^16.8.1",
45
+ "typescript": "^5.3.3"
46
+ }
47
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * GraphQL query and mutation documents for Apollo Client
3
+ * These can be used directly with Apollo Client's useQuery and useMutation hooks
4
+ */
5
+
6
+ export const LOGIN_MUTATION = `
7
+ mutation Login($input: LoginInput!) {
8
+ login(input: $input) {
9
+ user {
10
+ id
11
+ email
12
+ username
13
+ name
14
+ accountStatus
15
+ emailVerified
16
+ createdAt
17
+ updatedAt
18
+ }
19
+ token
20
+ }
21
+ }
22
+ `;
23
+
24
+ export const REGISTER_MUTATION = `
25
+ mutation Register($input: RegisterInput!) {
26
+ register(input: $input) {
27
+ user {
28
+ id
29
+ email
30
+ username
31
+ name
32
+ accountStatus
33
+ emailVerified
34
+ createdAt
35
+ updatedAt
36
+ }
37
+ token
38
+ }
39
+ }
40
+ `;
41
+
42
+ export const LOGOUT_MUTATION = `
43
+ mutation Logout {
44
+ logout
45
+ }
46
+ `;
47
+
48
+ export const ME_QUERY = `
49
+ query Me {
50
+ me {
51
+ id
52
+ email
53
+ username
54
+ name
55
+ accountStatus
56
+ emailVerified
57
+ createdAt
58
+ updatedAt
59
+ }
60
+ }
61
+ `;
62
+
63
+ export const UPDATE_PROFILE_MUTATION = `
64
+ mutation UpdateProfile($input: UpdateProfileInput!) {
65
+ updateProfile(input: $input) {
66
+ id
67
+ email
68
+ username
69
+ name
70
+ accountStatus
71
+ emailVerified
72
+ createdAt
73
+ updatedAt
74
+ }
75
+ }
76
+ `;
77
+
78
+ export const UPDATE_PASSWORD_MUTATION = `
79
+ mutation UpdatePassword($input: UpdatePasswordInput!) {
80
+ updatePassword(input: $input)
81
+ }
82
+ `;
83
+
84
+ export const REQUEST_PASSWORD_RESET_MUTATION = `
85
+ mutation RequestPasswordReset($email: String!) {
86
+ requestPasswordReset(email: $email)
87
+ }
88
+ `;
89
+
90
+ export const RESET_PASSWORD_MUTATION = `
91
+ mutation ResetPassword($code: String!, $newPassword: String!) {
92
+ resetPassword(code: $code, newPassword: $newPassword)
93
+ }
94
+ `;
95
+
96
+ export const CHECK_PASSWORD_STRENGTH_QUERY = `
97
+ query CheckPasswordStrength($password: String!) {
98
+ checkPasswordStrength(password: $password) {
99
+ score
100
+ feedback
101
+ }
102
+ }
103
+ `;
104
+
105
+ /**
106
+ * Fragment for User fields
107
+ * Can be composed into other queries
108
+ */
109
+ export const USER_FIELDS_FRAGMENT = `
110
+ fragment UserFields on User {
111
+ id
112
+ email
113
+ username
114
+ name
115
+ accountStatus
116
+ emailVerified
117
+ createdAt
118
+ updatedAt
119
+ }
120
+ `;
121
+
122
+ /**
123
+ * Fragment for AuthPayload fields
124
+ */
125
+ export const AUTH_PAYLOAD_FRAGMENT = `
126
+ fragment AuthPayloadFields on AuthPayload {
127
+ user {
128
+ ...UserFields
129
+ }
130
+ token
131
+ }
132
+ ${USER_FIELDS_FRAGMENT}
133
+ `;
package/src/index.ts ADDED
@@ -0,0 +1,29 @@
1
+ export { typeDefs } from './typeDefs';
2
+ export { createResolvers } from './resolvers';
3
+ export type { AuthDataSource, AuthContext, ResolverDependencies } from './resolvers';
4
+
5
+ export {
6
+ LOGIN_MUTATION,
7
+ REGISTER_MUTATION,
8
+ LOGOUT_MUTATION,
9
+ ME_QUERY,
10
+ UPDATE_PROFILE_MUTATION,
11
+ UPDATE_PASSWORD_MUTATION,
12
+ REQUEST_PASSWORD_RESET_MUTATION,
13
+ RESET_PASSWORD_MUTATION,
14
+ CHECK_PASSWORD_STRENGTH_QUERY,
15
+ USER_FIELDS_FRAGMENT,
16
+ AUTH_PAYLOAD_FRAGMENT,
17
+ } from './documents';
18
+
19
+ // Re-export types
20
+ export type {
21
+ User,
22
+ AuthUser,
23
+ LoginInput,
24
+ RegisterInput,
25
+ UpdateProfileInput,
26
+ UpdatePasswordInput,
27
+ AuthPayload,
28
+ PasswordStrength,
29
+ } from '@appforgeapps/shieldforge-types';
@@ -0,0 +1,221 @@
1
+ import {
2
+ User,
3
+ AuthUser,
4
+ LoginInput,
5
+ RegisterInput,
6
+ UpdateProfileInput,
7
+ UpdatePasswordInput,
8
+ AuthPayload,
9
+ PasswordStrength,
10
+ } from '@appforgeapps/shieldforge-types';
11
+
12
+ /**
13
+ * Data source functions that must be implemented by the consumer
14
+ */
15
+ export interface AuthDataSource {
16
+ // User operations
17
+ getUserById(id: string): Promise<User | null>;
18
+ getUserByEmail(email: string): Promise<User | null>;
19
+ createUser(input: RegisterInput & { passwordHash: string }): Promise<User>;
20
+ updateUser(id: string, input: Partial<User>): Promise<User>;
21
+
22
+ // Password reset operations
23
+ createPasswordReset(userId: string, code: string, expiresAt: Date): Promise<void>;
24
+ getPasswordReset(code: string): Promise<{ userId: string; expiresAt: Date } | null>;
25
+ deletePasswordReset(code: string): Promise<void>;
26
+ }
27
+
28
+ /**
29
+ * Context interface that must be provided to resolvers
30
+ */
31
+ export interface AuthContext {
32
+ userId?: string;
33
+ token?: string;
34
+ }
35
+
36
+ /**
37
+ * Resolver dependencies
38
+ */
39
+ export interface ResolverDependencies {
40
+ dataSource: AuthDataSource;
41
+ auth: {
42
+ hashPassword(password: string): Promise<string>;
43
+ verifyPassword(password: string, hash: string): Promise<boolean>;
44
+ generateToken(payload: { userId: string; email: string }): string;
45
+ verifyToken(token: string): { userId: string; email: string };
46
+ calculatePasswordStrength(password: string): PasswordStrength;
47
+ sanitizeUser(user: User): AuthUser;
48
+ generateResetCode(length?: number): string;
49
+ sendPasswordResetEmail(to: string, code: string, resetUrl?: string): Promise<void>;
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Create GraphQL resolvers with dependency injection
55
+ */
56
+ export function createResolvers(deps: ResolverDependencies) {
57
+ const { dataSource, auth } = deps;
58
+
59
+ return {
60
+ Query: {
61
+ me: async (_parent: any, _args: any, context: AuthContext): Promise<AuthUser | null> => {
62
+ if (!context.userId) {
63
+ return null;
64
+ }
65
+ const user = await dataSource.getUserById(context.userId);
66
+ return user ? auth.sanitizeUser(user) : null;
67
+ },
68
+
69
+ checkPasswordStrength: (_parent: any, args: { password: string }): PasswordStrength => {
70
+ return auth.calculatePasswordStrength(args.password);
71
+ },
72
+ },
73
+
74
+ Mutation: {
75
+ login: async (_parent: any, args: { input: LoginInput }): Promise<AuthPayload> => {
76
+ const { email, password } = args.input;
77
+
78
+ const user = await dataSource.getUserByEmail(email);
79
+ if (!user || !user.passwordHash) {
80
+ throw new Error('Invalid email or password');
81
+ }
82
+
83
+ const isValid = await auth.verifyPassword(password, user.passwordHash);
84
+ if (!isValid) {
85
+ throw new Error('Invalid email or password');
86
+ }
87
+
88
+ const token = auth.generateToken({ userId: user.id, email: user.email });
89
+
90
+ return {
91
+ user: auth.sanitizeUser(user),
92
+ token,
93
+ };
94
+ },
95
+
96
+ register: async (_parent: any, args: { input: RegisterInput }): Promise<AuthPayload> => {
97
+ const { email, password, username, name } = args.input;
98
+
99
+ // Check if user already exists
100
+ const existingUser = await dataSource.getUserByEmail(email);
101
+ if (existingUser) {
102
+ throw new Error('User with this email already exists');
103
+ }
104
+
105
+ // Validate password strength
106
+ const strength = auth.calculatePasswordStrength(password);
107
+ if (strength.score < 2) {
108
+ throw new Error(`Password is too weak: ${strength.feedback.join(', ')}`);
109
+ }
110
+
111
+ // Hash password and create user
112
+ const passwordHash = await auth.hashPassword(password);
113
+ const user = await dataSource.createUser({
114
+ email,
115
+ password,
116
+ username,
117
+ name,
118
+ passwordHash,
119
+ });
120
+
121
+ const token = auth.generateToken({ userId: user.id, email: user.email });
122
+
123
+ return {
124
+ user: auth.sanitizeUser(user),
125
+ token,
126
+ };
127
+ },
128
+
129
+ logout: (_parent: any, _args: any, _context: AuthContext): boolean => {
130
+ // Logout is typically handled client-side by removing the token
131
+ // Server-side can optionally invalidate the token if using a token blacklist
132
+ return true;
133
+ },
134
+
135
+ updateProfile: async (
136
+ _parent: any,
137
+ args: { input: UpdateProfileInput },
138
+ context: AuthContext
139
+ ): Promise<AuthUser> => {
140
+ if (!context.userId) {
141
+ throw new Error('Not authenticated');
142
+ }
143
+
144
+ const user = await dataSource.updateUser(context.userId, args.input);
145
+ return auth.sanitizeUser(user);
146
+ },
147
+
148
+ updatePassword: async (
149
+ _parent: any,
150
+ args: { input: UpdatePasswordInput },
151
+ context: AuthContext
152
+ ): Promise<boolean> => {
153
+ if (!context.userId) {
154
+ throw new Error('Not authenticated');
155
+ }
156
+
157
+ const user = await dataSource.getUserById(context.userId);
158
+ if (!user || !user.passwordHash) {
159
+ throw new Error('User not found');
160
+ }
161
+
162
+ const isValid = await auth.verifyPassword(args.input.currentPassword, user.passwordHash);
163
+ if (!isValid) {
164
+ throw new Error('Current password is incorrect');
165
+ }
166
+
167
+ const strength = auth.calculatePasswordStrength(args.input.newPassword);
168
+ if (strength.score < 2) {
169
+ throw new Error(`New password is too weak: ${strength.feedback.join(', ')}`);
170
+ }
171
+
172
+ const newPasswordHash = await auth.hashPassword(args.input.newPassword);
173
+ await dataSource.updateUser(context.userId, { passwordHash: newPasswordHash });
174
+
175
+ return true;
176
+ },
177
+
178
+ requestPasswordReset: async (_parent: any, args: { email: string }): Promise<boolean> => {
179
+ const user = await dataSource.getUserByEmail(args.email);
180
+ if (!user) {
181
+ // Don't reveal whether the email exists
182
+ return true;
183
+ }
184
+
185
+ const resetCode = auth.generateResetCode();
186
+ const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
187
+
188
+ await dataSource.createPasswordReset(user.id, resetCode, expiresAt);
189
+ await auth.sendPasswordResetEmail(user.email, resetCode);
190
+
191
+ return true;
192
+ },
193
+
194
+ resetPassword: async (
195
+ _parent: any,
196
+ args: { code: string; newPassword: string }
197
+ ): Promise<boolean> => {
198
+ const reset = await dataSource.getPasswordReset(args.code);
199
+ if (!reset) {
200
+ throw new Error('Invalid or expired reset code');
201
+ }
202
+
203
+ if (new Date() > reset.expiresAt) {
204
+ await dataSource.deletePasswordReset(args.code);
205
+ throw new Error('Reset code has expired');
206
+ }
207
+
208
+ const strength = auth.calculatePasswordStrength(args.newPassword);
209
+ if (strength.score < 2) {
210
+ throw new Error(`New password is too weak: ${strength.feedback.join(', ')}`);
211
+ }
212
+
213
+ const newPasswordHash = await auth.hashPassword(args.newPassword);
214
+ await dataSource.updateUser(reset.userId, { passwordHash: newPasswordHash });
215
+ await dataSource.deletePasswordReset(args.code);
216
+
217
+ return true;
218
+ },
219
+ },
220
+ };
221
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * GraphQL type definitions for authentication
3
+ */
4
+ export const typeDefs = `
5
+ enum UserAccountStatus {
6
+ ACTIVE
7
+ INACTIVE
8
+ SUSPENDED
9
+ PENDING
10
+ }
11
+
12
+ type User {
13
+ id: ID!
14
+ email: String!
15
+ username: String
16
+ name: String
17
+ accountStatus: UserAccountStatus
18
+ emailVerified: Boolean
19
+ createdAt: String
20
+ updatedAt: String
21
+ }
22
+
23
+ type AuthPayload {
24
+ user: User!
25
+ token: String!
26
+ }
27
+
28
+ input LoginInput {
29
+ email: String!
30
+ password: String!
31
+ }
32
+
33
+ input RegisterInput {
34
+ email: String!
35
+ password: String!
36
+ username: String
37
+ name: String
38
+ }
39
+
40
+ input UpdateProfileInput {
41
+ username: String
42
+ name: String
43
+ email: String
44
+ }
45
+
46
+ input UpdatePasswordInput {
47
+ currentPassword: String!
48
+ newPassword: String!
49
+ }
50
+
51
+ type PasswordStrength {
52
+ score: Int!
53
+ feedback: [String!]!
54
+ }
55
+
56
+ type Query {
57
+ me: User
58
+ checkPasswordStrength(password: String!): PasswordStrength!
59
+ }
60
+
61
+ type Mutation {
62
+ login(input: LoginInput!): AuthPayload!
63
+ register(input: RegisterInput!): AuthPayload!
64
+ logout: Boolean!
65
+ updateProfile(input: UpdateProfileInput!): User!
66
+ updatePassword(input: UpdatePasswordInput!): Boolean!
67
+ requestPasswordReset(email: String!): Boolean!
68
+ resetPassword(code: String!, newPassword: String!): Boolean!
69
+ }
70
+ `;